diff options
Diffstat (limited to 'core/java')
162 files changed, 6066 insertions, 3684 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index a9eaf29..3f1845a 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -16,6 +16,7 @@ package android.accessibilityservice; +import android.annotation.NonNull; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -27,6 +28,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; @@ -618,6 +620,23 @@ public abstract class AccessibilityService extends Service { } } + @Override + public Object getSystemService(@ServiceName @NonNull String name) { + if (getBaseContext() == null) { + throw new IllegalStateException( + "System services not available to Activities before onCreate()"); + } + + // Guarantee that we always return the same window manager instance. + if (WINDOW_SERVICE.equals(name)) { + if (mWindowManager == null) { + mWindowManager = (WindowManager) getBaseContext().getSystemService(name); + } + return mWindowManager; + } + return super.getSystemService(name); + } + /** * Implement to return the implementation of the internal accessibility * service interface. @@ -645,8 +664,10 @@ public abstract class AccessibilityService extends Service { mConnectionId = connectionId; mWindowToken = windowToken; - // Let the window manager know about our shiny new token. - WindowManagerGlobal.getInstance().setDefaultToken(mWindowToken); + // The client may have already obtained the window manager, so + // update the default token on whatever manager we gave them. + final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE); + wm.setDefaultToken(windowToken); } @Override diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 09b484b..6957435 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -17,37 +17,37 @@ package android.accounts; import android.app.Activity; -import android.content.Intent; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.IntentFilter; -import android.content.BroadcastReceiver; import android.content.res.Resources; import android.database.SQLException; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.os.RemoteException; import android.os.Parcelable; -import android.os.Build; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; -import android.util.Log; import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; +import com.google.android.collect.Maps; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; -import java.util.HashMap; -import java.util.Map; - -import com.android.internal.R; -import com.google.android.collect.Maps; +import java.util.concurrent.TimeoutException; /** * This class provides access to a centralized registry of the user's @@ -747,13 +747,17 @@ public class AccountManager { * null for the main thread * @return An {@link AccountManagerFuture} which resolves to a Boolean, * true if the account has been successfully removed + * @deprecated use + * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)} + * instead */ + @Deprecated public AccountManagerFuture<Boolean> removeAccount(final Account account, AccountManagerCallback<Boolean> callback, Handler handler) { if (account == null) throw new IllegalArgumentException("account is null"); return new Future2Task<Boolean>(handler, callback) { public void doWork() throws RemoteException { - mService.removeAccount(mResponse, account); + mService.removeAccount(mResponse, account, false); } public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { @@ -765,9 +769,60 @@ public class AccountManager { } /** + * Removes an account from the AccountManager. Does nothing if the account + * does not exist. Does not delete the account from the server. + * The authenticator may have its own policies preventing account + * deletion, in which case the account will not be deleted. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param account The {@link Account} to remove + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user to delete an + * account; used only to call startActivity(); if null, the prompt + * will not be launched directly, but the {@link Intent} may be + * returned to the caller instead + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle with + * {@link #KEY_BOOLEAN_RESULT} if activity was specified and an account + * was removed or if active. If no activity was specified, the returned + * Bundle contains only {@link #KEY_INTENT} with the {@link Intent} + * needed to launch the actual account removal process, if authenticator + * needs the activity launch. If an error occurred, + * {@link AccountManagerFuture#getResult()} throws: + * <ul> + * <li> {@link AuthenticatorException} if no authenticator was registered for + * this account type or the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation was canceled for + * any reason, including the user canceling the creation process or + * adding accounts (of this type) has been disabled by policy + * </ul> + */ + public AccountManagerFuture<Bundle> removeAccount(final Account account, + final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { + if (account == null) throw new IllegalArgumentException("account is null"); + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.removeAccount(mResponse, account, activity != null); + } + }.start(); + } + + /** * @see #removeAccount(Account, AccountManagerCallback, Handler) * @hide + * @deprecated use + * {@link #removeAccountAsUser(Account, Activity, AccountManagerCallback, Handler)} + * instead */ + @Deprecated public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account, AccountManagerCallback<Boolean> callback, Handler handler, final UserHandle userHandle) { @@ -775,7 +830,7 @@ public class AccountManager { if (userHandle == null) throw new IllegalArgumentException("userHandle is null"); return new Future2Task<Boolean>(handler, callback) { public void doWork() throws RemoteException { - mService.removeAccountAsUser(mResponse, account, userHandle.getIdentifier()); + mService.removeAccountAsUser(mResponse, account, false, userHandle.getIdentifier()); } public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { @@ -787,6 +842,52 @@ public class AccountManager { } /** + * @see #removeAccount(Account, Activity, AccountManagerCallback, Handler) + * @hide + */ + public AccountManagerFuture<Bundle> removeAccountAsUser(final Account account, + final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler, + final UserHandle userHandle) { + if (account == null) + throw new IllegalArgumentException("account is null"); + if (userHandle == null) + throw new IllegalArgumentException("userHandle is null"); + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.removeAccountAsUser(mResponse, account, activity != null, + userHandle.getIdentifier()); + } + }.start(); + } + + /** + * Removes an account directly. Normally used by authenticators, not + * directly by applications. Does not delete the account from the server. + * The authenticator may have its own policies preventing account deletion, + * in which case the account will not be deleted. + * <p> + * It is safe to call this method from the main thread. + * <p> + * This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and to have the + * same UID or signature as the account's authenticator. + * + * @param account The {@link Account} to delete. + * @return True if the account was successfully deleted, false if the + * account did not exist, the account is null, or another error + * occurs. + */ + public boolean removeAccountExplicitly(Account account) { + if (account == null) throw new IllegalArgumentException("account is null"); + try { + return mService.removeAccountExplicitly(account); + } catch (RemoteException e) { + // won't ever happen + throw new RuntimeException(e); + } + } + + /** * Removes an auth token from the AccountManager's cache. Does nothing if * the auth token is not currently in the cache. Applications must call this * method when the auth token is found to have expired or otherwise become @@ -1342,6 +1443,40 @@ public class AccountManager { } /** + * Copies an account from the primary user to another user. + * @param account the account to copy + * @param user the target user + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it + * succeeded. + * @hide + */ + public AccountManagerFuture<Boolean> copyAccountToUser( + final Account account, final UserHandle user, + AccountManagerCallback<Boolean> callback, Handler handler) { + if (account == null) throw new IllegalArgumentException("account is null"); + if (user == null) throw new IllegalArgumentException("user is null"); + + return new Future2Task<Boolean>(handler, callback) { + @Override + public void doWork() throws RemoteException { + mService.copyAccountToUser( + mResponse, account, UserHandle.USER_OWNER, user.getIdentifier()); + } + @Override + public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { + if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { + throw new AuthenticatorException("no result in response"); + } + return bundle.getBoolean(KEY_BOOLEAN_RESULT); + } + }.start(); + } + + /** * @hide * Removes the shared account. * @param account the account to remove diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index a133788..aa41161 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -37,8 +37,13 @@ interface IAccountManager { void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features); void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features); boolean addAccountExplicitly(in Account account, String password, in Bundle extras); - void removeAccount(in IAccountManagerResponse response, in Account account); - void removeAccountAsUser(in IAccountManagerResponse response, in Account account, int userId); + void removeAccount(in IAccountManagerResponse response, in Account account, + boolean expectActivityLaunch); + void removeAccountAsUser(in IAccountManagerResponse response, in Account account, + boolean expectActivityLaunch, int userId); + boolean removeAccountExplicitly(in Account account); + void copyAccountToUser(in IAccountManagerResponse response, in Account account, + int userFrom, int userTo); void invalidateAuthToken(String accountType, String authToken); String peekAuthToken(in Account account, String authTokenType); void setAuthToken(in Account account, String authTokenType, String authToken); diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java index f9aa00e..1738ade 100644 --- a/core/java/android/animation/TimeAnimator.java +++ b/core/java/android/animation/TimeAnimator.java @@ -1,5 +1,23 @@ +/* + * Copyright (C) 2010 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.animation; +import android.view.animation.AnimationUtils; + /** * This class provides a simple callback mechanism to listeners that is synchronized with all * other animators in the system. There is no duration, interpolation, or object value-setting @@ -29,6 +47,13 @@ public class TimeAnimator extends ValueAnimator { return false; } + @Override + public void setCurrentPlayTime(long playTime) { + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + mStartTime = Math.max(mStartTime, currentTime - playTime); + animationFrame(currentTime); + } + /** * Sets a listener that is sent update events throughout the life of * an animation. diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 07f79b8..d65b490 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -79,7 +79,7 @@ public class ValueAnimator extends Animator { * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked * to a value. */ - long mSeekTime = -1; + float mSeekFraction = -1; /** * Set on the next frame after pause() is called, used to calculate a new startTime @@ -537,14 +537,31 @@ public class ValueAnimator extends Animator { * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. */ public void setCurrentPlayTime(long playTime) { + float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : + playTime == 0 ? 0 : 1; + setCurrentFraction(fraction); + } + + /** + * Sets the position of the animation to the specified fraction. This fraction should + * be between 0 and the total fraction of the animation, including any repetition. That is, + * a fraction of 0 will position the animation at the beginning, a value of 1 at the end, + * and a value of 2 at the beginning of a reversing animator that repeats once. If + * the animation has not yet been started, then it will not advance forward after it is + * set to this fraction; it will simply set the fraction to this value and perform any + * appropriate actions based on that fraction. If the animation is already running, then + * setCurrentFraction() will set the current fraction to this value and continue + * playing from that point. + * + * @param fraction The fraction to which the animation is advanced or rewound. + */ + public void setCurrentFraction(float fraction) { initAnimation(); - long currentTime = AnimationUtils.currentAnimationTimeMillis(); if (mPlayingState != RUNNING) { - mSeekTime = playTime; + mSeekFraction = fraction; mPlayingState = SEEKED; } - mStartTime = currentTime - playTime; - doAnimationFrame(currentTime); + animateValue(fraction); } /** @@ -948,6 +965,7 @@ public class ValueAnimator extends Animator { } mPlayingBackwards = playBackwards; mCurrentIteration = 0; + int prevPlayingState = mPlayingState; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; @@ -957,7 +975,9 @@ public class ValueAnimator extends Animator { animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running - setCurrentPlayTime(0); + if (prevPlayingState != SEEKED) { + setCurrentPlayTime(0); + } mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); @@ -1221,12 +1241,12 @@ public class ValueAnimator extends Animator { final boolean doAnimationFrame(long frameTime) { if (mPlayingState == STOPPED) { mPlayingState = RUNNING; - if (mSeekTime < 0) { + if (mSeekFraction < 0) { mStartTime = frameTime; } else { - mStartTime = frameTime - mSeekTime; - // Now that we're playing, reset the seek time - mSeekTime = -1; + long seekTime = (long) (mDuration * mSeekFraction); + mStartTime = frameTime - seekTime; + mSeekFraction = -1; } } if (mPaused) { @@ -1292,7 +1312,7 @@ public class ValueAnimator extends Animator { if (mUpdateListeners != null) { anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners); } - anim.mSeekTime = -1; + anim.mSeekFraction = -1; anim.mPlayingBackwards = false; anim.mCurrentIteration = 0; anim.mInitialized = false; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 148527f..fab88a2 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5620,6 +5620,16 @@ public class Activity extends ContextThemeWrapper } /** + * @hide + */ + public void dispatchEnterAnimationComplete() { + onEnterAnimationComplete(); + if (getWindow() != null && getWindow().getDecorView() != null) { + getWindow().getDecorView().getViewTreeObserver().dispatchOnEnterAnimationComplete(); + } + } + + /** * Adjust the current immersive mode setting. * * Note that changing this value will have no effect on the activity's diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 2a17fa6..d56dc1e 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -23,8 +23,8 @@ package android.app; */ public abstract class ActivityManagerInternal { // Called by the power manager. - public abstract void goingToSleep(); - public abstract void wakingUp(); + public abstract void onWakefulnessChanged(int wakefulness); + public abstract int startIsolatedProcess(String entryPoint, String[] mainArgs, String processName, String abiOverride, int uid, Runnable crashHandler); } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index c3028b7..6ec48e5 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1186,6 +1186,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case CHECK_PERMISSION_WITH_TOKEN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String perm = data.readString(); + int pid = data.readInt(); + int uid = data.readInt(); + IBinder token = data.readStrongBinder(); + int res = checkPermissionWithToken(perm, pid, uid, token); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + case CHECK_URI_PERMISSION_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); Uri uri = Uri.CREATOR.createFromParcel(data); @@ -1193,7 +1205,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM int uid = data.readInt(); int mode = data.readInt(); int userId = data.readInt(); - int res = checkUriPermission(uri, pid, uid, mode, userId); + IBinder callerToken = data.readStrongBinder(); + int res = checkUriPermission(uri, pid, uid, mode, userId, callerToken); reply.writeNoException(); reply.writeInt(res); return true; @@ -3742,7 +3755,7 @@ class ActivityManagerProxy implements IActivityManager mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0); reply.readException(); IIntentSender res = IIntentSender.Stub.asInterface( - reply.readStrongBinder()); + reply.readStrongBinder()); data.recycle(); reply.recycle(); return res; @@ -3851,6 +3864,22 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } + public int checkPermissionWithToken(String permission, int pid, int uid, IBinder callerToken) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(permission); + data.writeInt(pid); + data.writeInt(uid); + data.writeStrongBinder(callerToken); + mRemote.transact(CHECK_PERMISSION_WITH_TOKEN_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer, final int userId) throws RemoteException { Parcel data = Parcel.obtain(); @@ -3866,8 +3895,8 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } - public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId) - throws RemoteException { + public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId, + IBinder callerToken) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3876,6 +3905,7 @@ class ActivityManagerProxy implements IActivityManager data.writeInt(uid); data.writeInt(mode); data.writeInt(userId); + data.writeStrongBinder(callerToken); mRemote.transact(CHECK_URI_PERMISSION_TRANSACTION, data, reply, 0); reply.readException(); int res = reply.readInt(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a268b1c..b64e724 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -84,6 +84,8 @@ import android.util.Slog; import android.util.SuperNotCalledException; import android.view.Display; import android.view.HardwareRenderer; +import android.view.IWindowManager; +import android.view.IWindowSessionCallback; import android.view.View; import android.view.ViewDebug; import android.view.ViewManager; @@ -2366,6 +2368,9 @@ public final class ActivityThread { if (localLOGV) Slog.v( TAG, "Handling launch of " + r); + // Initialize before creating the activity + WindowManagerGlobal.initialize(); + Activity a = performLaunchActivity(r, customIntent); if (a != null) { @@ -2531,7 +2536,7 @@ public final class ActivityThread { private void handleEnterAnimationComplete(IBinder token) { ActivityClientRecord r = mActivities.get(token); if (r != null) { - r.activity.onEnterAnimationComplete(); + r.activity.dispatchEnterAnimationComplete(); } } @@ -5223,8 +5228,6 @@ public final class ActivityThread { sMainThreadHandler = thread.getHandler(); } - AsyncTask.init(); - if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 78cc810..12fee2d 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -20,9 +20,9 @@ import android.app.usage.IUsageStatsManager; import android.app.usage.UsageStatsManager; import android.appwidget.AppWidgetManager; import android.os.Build; - import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; + import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.policy.PolicyManager; import com.android.internal.util.Preconditions; @@ -127,6 +127,7 @@ import android.print.PrintManager; import android.service.fingerprint.IFingerprintService; import android.service.fingerprint.FingerprintManager; import android.telecom.TelecomManager; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.content.ClipboardManager; import android.util.AndroidRuntimeException; @@ -563,6 +564,11 @@ class ContextImpl extends Context { return new TelephonyManager(ctx.getOuterContext()); }}); + registerService(TELEPHONY_SUBSCRIPTION_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new SubscriptionManager(ctx.getOuterContext()); + }}); + registerService(TELECOM_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new TelecomManager(ctx.getOuterContext()); @@ -1872,6 +1878,21 @@ class ContextImpl extends Context { } } + /** @hide */ + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + if (permission == null) { + throw new IllegalArgumentException("permission is null"); + } + + try { + return ActivityManagerNative.getDefault().checkPermissionWithToken( + permission, pid, uid, callerToken); + } catch (RemoteException e) { + return PackageManager.PERMISSION_DENIED; + } + } + @Override public int checkCallingPermission(String permission) { if (permission == null) { @@ -1960,7 +1981,19 @@ class ContextImpl extends Context { try { return ActivityManagerNative.getDefault().checkUriPermission( ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags, - resolveUserId(uri)); + resolveUserId(uri), null); + } catch (RemoteException e) { + return PackageManager.PERMISSION_DENIED; + } + } + + /** @hide */ + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { + try { + return ActivityManagerNative.getDefault().checkUriPermission( + ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags, + resolveUserId(uri), callerToken); } catch (RemoteException e) { return PackageManager.PERMISSION_DENIED; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 6433f3f..5362303 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -219,9 +219,11 @@ public interface IActivityManager extends IInterface { public int checkPermission(String permission, int pid, int uid) throws RemoteException; - - public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId) + public int checkPermissionWithToken(String permission, int pid, int uid, IBinder callerToken) throws RemoteException; + + public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId, + IBinder callerToken) throws RemoteException; public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode, int userId) throws RemoteException; public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode, int userId) @@ -785,4 +787,5 @@ public interface IActivityManager extends IInterface { int GET_TASK_DESCRIPTION_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+238; int LAUNCH_ASSIST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+239; int START_IN_PLACE_ANIMATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+240; + int CHECK_PERMISSION_WITH_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+241; } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 5038df9..ddd21e6 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -225,28 +225,28 @@ public class KeyguardManager { } /** - * Return whether unlocking the device is currently not requiring a password - * because of a trust agent. + * Returns whether the device is currently locked and requires a PIN, pattern or + * password to unlock. * - * @return true if the keyguard can currently be unlocked without entering credentials - * because the device is in a trusted environment. + * @return true if unlocking the device currently requires a PIN, pattern or + * password. */ - public boolean isKeyguardInTrustedState() { - return isKeyguardInTrustedState(UserHandle.getCallingUserId()); + public boolean isDeviceLocked() { + return isDeviceLocked(UserHandle.getCallingUserId()); } /** - * Return whether unlocking the device is currently not requiring a password - * because of a trust agent. + * Returns whether the device is currently locked and requires a PIN, pattern or + * password to unlock. * - * @param userId the user for which the trusted state should be reported. - * @return true if the keyguard can currently be unlocked without entering credentials - * because the device is in a trusted environment. + * @param userId the user for which the locked state should be reported. + * @return true if unlocking the device currently requires a PIN, pattern or + * password. * @hide */ - public boolean isKeyguardInTrustedState(int userId) { + public boolean isDeviceLocked(int userId) { try { - return mTrustManager.isTrusted(userId); + return mTrustManager.isDeviceLocked(userId); } catch (RemoteException e) { return false; } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index aa98e97..ece2a33 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -632,55 +632,60 @@ public final class LoadedApk { public void removeContextRegistrations(Context context, String who, String what) { final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled(); - ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap = - mReceivers.remove(context); - if (rmap != null) { - for (int i=0; i<rmap.size(); i++) { - LoadedApk.ReceiverDispatcher rd = rmap.valueAt(i); - IntentReceiverLeaked leak = new IntentReceiverLeaked( - what + " " + who + " has leaked IntentReceiver " - + rd.getIntentReceiver() + " that was " + - "originally registered here. Are you missing a " + - "call to unregisterReceiver()?"); - leak.setStackTrace(rd.getLocation().getStackTrace()); - Slog.e(ActivityThread.TAG, leak.getMessage(), leak); - if (reportRegistrationLeaks) { - StrictMode.onIntentReceiverLeaked(leak); - } - try { - ActivityManagerNative.getDefault().unregisterReceiver( - rd.getIIntentReceiver()); - } catch (RemoteException e) { - // system crashed, nothing we can do + synchronized (mReceivers) { + ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap = + mReceivers.remove(context); + if (rmap != null) { + for (int i = 0; i < rmap.size(); i++) { + LoadedApk.ReceiverDispatcher rd = rmap.valueAt(i); + IntentReceiverLeaked leak = new IntentReceiverLeaked( + what + " " + who + " has leaked IntentReceiver " + + rd.getIntentReceiver() + " that was " + + "originally registered here. Are you missing a " + + "call to unregisterReceiver()?"); + leak.setStackTrace(rd.getLocation().getStackTrace()); + Slog.e(ActivityThread.TAG, leak.getMessage(), leak); + if (reportRegistrationLeaks) { + StrictMode.onIntentReceiverLeaked(leak); + } + try { + ActivityManagerNative.getDefault().unregisterReceiver( + rd.getIIntentReceiver()); + } catch (RemoteException e) { + // system crashed, nothing we can do + } } } + mUnregisteredReceivers.remove(context); } - mUnregisteredReceivers.remove(context); - //Slog.i(TAG, "Receiver registrations: " + mReceivers); - ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap = - mServices.remove(context); - if (smap != null) { - for (int i=0; i<smap.size(); i++) { - LoadedApk.ServiceDispatcher sd = smap.valueAt(i); - ServiceConnectionLeaked leak = new ServiceConnectionLeaked( - what + " " + who + " has leaked ServiceConnection " - + sd.getServiceConnection() + " that was originally bound here"); - leak.setStackTrace(sd.getLocation().getStackTrace()); - Slog.e(ActivityThread.TAG, leak.getMessage(), leak); - if (reportRegistrationLeaks) { - StrictMode.onServiceConnectionLeaked(leak); - } - try { - ActivityManagerNative.getDefault().unbindService( - sd.getIServiceConnection()); - } catch (RemoteException e) { - // system crashed, nothing we can do + + synchronized (mServices) { + //Slog.i(TAG, "Receiver registrations: " + mReceivers); + ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap = + mServices.remove(context); + if (smap != null) { + for (int i = 0; i < smap.size(); i++) { + LoadedApk.ServiceDispatcher sd = smap.valueAt(i); + ServiceConnectionLeaked leak = new ServiceConnectionLeaked( + what + " " + who + " has leaked ServiceConnection " + + sd.getServiceConnection() + " that was originally bound here"); + leak.setStackTrace(sd.getLocation().getStackTrace()); + Slog.e(ActivityThread.TAG, leak.getMessage(), leak); + if (reportRegistrationLeaks) { + StrictMode.onServiceConnectionLeaked(leak); + } + try { + ActivityManagerNative.getDefault().unbindService( + sd.getIServiceConnection()); + } catch (RemoteException e) { + // system crashed, nothing we can do + } + sd.doForget(); } - sd.doForget(); } + mUnboundServices.remove(context); + //Slog.i(TAG, "Service registrations: " + mServices); } - mUnboundServices.remove(context); - //Slog.i(TAG, "Service registrations: " + mServices); } public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r, diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index dfe5cf5..9e8a793 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4115,9 +4115,9 @@ public class Notification implements Parcelable * <pre class="prettyprint"> * Notification noti = new Notification.Builder() * .setSmallIcon(R.drawable.ic_stat_player) - * .setContentTitle("Track title") // these three lines are optional - * .setContentText("Artist - Album") // if you use - * .setLargeIcon(albumArtBitmap)) // setMediaSession(token) + * .setContentTitle("Track title") + * .setContentText("Artist - Album") + * .setLargeIcon(albumArtBitmap)) * .setStyle(<b>new Notification.MediaStyle()</b> * .setMediaSession(mySession)) * .build(); @@ -4986,7 +4986,9 @@ public class Notification implements Parcelable } /** - * Set a hint that this notification's background should not be clipped if possible. + * Set a hint that this notification's background should not be clipped if possible, + * and should instead be resized to fully display on the screen, retaining the aspect + * ratio of the image. This can be useful for images like barcodes or qr codes. * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. * @return this object for method chaining */ @@ -4997,7 +4999,9 @@ public class Notification implements Parcelable } /** - * Get a hint that this notification's background should not be clipped if possible. + * Get a hint that this notification's background should not be clipped if possible, + * and should instead be resized to fully display on the screen, retaining the aspect + * ratio of the image. This can be useful for images like barcodes or qr codes. * @return {@code true} if it's ok if the background is clipped on the screen, false * otherwise. The default value is {@code false} if this was never set. */ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 33cac75..50d6b11 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -52,11 +52,15 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; +import java.security.KeyFactory; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -142,13 +146,24 @@ public class DevicePolicyManager { * {@link #ACTION_PROVISION_MANAGED_PROFILE} this package has to match the package name of the * application that started provisioning. The package will be set as profile owner in that case. * - * <p>This package is set as device owner when device owner provisioning is started by an Nfc - * message containing an Nfc record with MIME type {@link #MIME_TYPE_PROVISIONING_NFC}. + * <p>This package is set as device owner when device owner provisioning is started by an NFC + * message containing an NFC record with MIME type {@link #MIME_TYPE_PROVISIONING_NFC}. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME"; /** + * An {@link android.accounts.Account} extra holding the account to migrate during managed + * profile provisioning. If the account supplied is present in the primary user, it will be + * copied, along with its credentials to the managed profile and removed from the primary user. + * + * Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}. + */ + + public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE + = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE"; + + /** * A String extra that, holds the email address of the account which a managed profile is * created for. Used with {@link #ACTION_PROVISION_MANAGED_PROFILE} and * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}. @@ -165,11 +180,21 @@ public class DevicePolicyManager { = "android.app.extra.PROVISIONING_EMAIL_ADDRESS"; /** + * A Boolean extra that can be used by the mobile device management application to skip the + * disabling of system apps during provisioning when set to <code>true</code>. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = + "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED"; + + /** * A String extra holding the time zone {@link android.app.AlarmManager} that the device * will be set to. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; @@ -178,8 +203,8 @@ public class DevicePolicyManager { * A Long extra holding the wall clock time (in milliseconds) to be set on the device's * {@link android.app.AlarmManager}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME"; @@ -188,8 +213,8 @@ public class DevicePolicyManager { * A String extra holding the {@link java.util.Locale} that the device will be set to. * Format: xx_yy, where xx is the language code, and yy the country code. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE"; @@ -198,8 +223,8 @@ public class DevicePolicyManager { * A String extra holding the ssid of the wifi network that should be used during nfc device * owner provisioning for downloading the mobile device management application. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID"; @@ -208,8 +233,8 @@ public class DevicePolicyManager { * A boolean extra indicating whether the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID} * is hidden or not. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN"; @@ -218,8 +243,8 @@ public class DevicePolicyManager { * A String extra indicating the security type of the wifi network in * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE"; @@ -228,8 +253,8 @@ public class DevicePolicyManager { * A String extra holding the password of the wifi network in * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_PASSWORD = "android.app.extra.PROVISIONING_WIFI_PASSWORD"; @@ -238,8 +263,8 @@ public class DevicePolicyManager { * A String extra holding the proxy host for the wifi network in * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_PROXY_HOST = "android.app.extra.PROVISIONING_WIFI_PROXY_HOST"; @@ -248,8 +273,8 @@ public class DevicePolicyManager { * An int extra holding the proxy port for the wifi network in * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT"; @@ -258,8 +283,8 @@ public class DevicePolicyManager { * A String extra holding the proxy bypass for the wifi network in * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_PROXY_BYPASS = "android.app.extra.PROVISIONING_WIFI_PROXY_BYPASS"; @@ -268,8 +293,8 @@ public class DevicePolicyManager { * A String extra holding the proxy auto-config (PAC) URL for the wifi network in * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_WIFI_PAC_URL = "android.app.extra.PROVISIONING_WIFI_PAC_URL"; @@ -278,8 +303,8 @@ public class DevicePolicyManager { * A String extra holding a url that specifies the download location of the device admin * package. When not provided it is assumed that the device admin package is already installed. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION"; @@ -288,8 +313,8 @@ public class DevicePolicyManager { * A String extra holding a http cookie header which should be used in the http request to the * url specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER"; @@ -300,8 +325,8 @@ public class DevicePolicyManager { * the file at the download location an error will be shown to the user and the user will be * asked to factory reset the device. * - * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an Nfc bump. + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM"; @@ -317,9 +342,9 @@ public class DevicePolicyManager { * <p> A typical use case would be a device that is owned by a company, but used by either an * employee or client. * - * <p> The Nfc message should be send to an unprovisioned device. + * <p> The NFC message should be send to an unprovisioned device. * - * <p>The Nfc record must contain a serialized {@link java.util.Properties} object which + * <p>The NFC record must contain a serialized {@link java.util.Properties} object which * contains the following properties: * <ul> * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}</li> @@ -1324,6 +1349,24 @@ public class DevicePolicyManager { } /** + * Returns the profile with the smallest maximum failed passwords for wipe, + * for the given user. So for primary user, it might return the primary or + * a managed profile. For a secondary user, it would be the same as the + * user passed in. + * @hide Used only by Keyguard + */ + public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) { + if (mService != null) { + try { + return mService.getProfileWithMinimumFailedPasswordsForWipe(userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return UserHandle.USER_NULL; + } + + /** * Flag for {@link #resetPassword}: don't allow other admins to change * the password again until the user has entered it. */ @@ -1342,13 +1385,16 @@ public class DevicePolicyManager { * characters when the requested quality is only numeric), in which case * the currently active quality will be increased to match. * + * <p>Calling with a null or empty password will clear any existing PIN, + * pattern or password if the current password constraints allow it. + * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call * this method; if it has not, a security exception will be thrown. * * <p>Calling this from a managed profile will throw a security exception. * - * @param password The new password for the user. + * @param password The new password for the user. Null or empty clears the password. * @param flags May be 0 or {@link #RESET_PASSWORD_REQUIRE_ENTRY}. * @return Returns true if the password was applied, or false if it is * not acceptable for the current constraints. @@ -1431,22 +1477,30 @@ public class DevicePolicyManager { /** * Flag for {@link #wipeData(int)}: also erase the device's external - * storage. + * storage (such as SD cards). */ public static final int WIPE_EXTERNAL_STORAGE = 0x0001; /** + * Flag for {@link #wipeData(int)}: also erase the factory reset protection + * data. + * + * This flag may only be set by device owner admins; if it is set by other + * admins a {@link SecurityException} will be thrown. + */ + public static final int WIPE_RESET_PROTECTION_DATA = 0x0002; + + /** * Ask the user data be wiped. This will cause the device to reboot, - * erasing all user data while next booting up. External storage such - * as SD cards will be also erased if the flag {@link #WIPE_EXTERNAL_STORAGE} - * is set. + * erasing all user data while next booting up. * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call * this method; if it has not, a security exception will be thrown. * - * @param flags Bit mask of additional options: currently 0 and - * {@link #WIPE_EXTERNAL_STORAGE} are supported. + * @param flags Bit mask of additional options: currently supported flags + * are {@link #WIPE_EXTERNAL_STORAGE} and + * {@link #WIPE_RESET_PROTECTION_DATA}. */ public void wipeData(int flags) { if (mService != null) { @@ -1868,13 +1922,15 @@ public class DevicePolicyManager { String alias) { try { final byte[] pemCert = Credentials.convertToPem(cert); - return mService.installKeyPair(who, privKey.getEncoded(), pemCert, alias); - } catch (CertificateException e) { - Log.w(TAG, "Error encoding certificate", e); - } catch (IOException e) { - Log.w(TAG, "Error writing certificate", e); + final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm()) + .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded(); + return mService.installKeyPair(who, pkcs8Key, pemCert, alias); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + Log.w(TAG, "Failed to obtain private key material", e); + } catch (CertificateException | IOException e) { + Log.w(TAG, "Could not pem-encode certificate", e); } return false; } @@ -3400,12 +3456,13 @@ public class DevicePolicyManager { } /** - * Called by profile or device owners to check whether a user has been blocked from - * uninstalling a package. + * Check whether the current user has been blocked by device policy from uninstalling a package. + * Requires the caller to be the profile owner if checking a specific admin's policy. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin The name of the admin component whose blocking policy will be checked, or null + * to check if any admin has blocked the uninstallation. * @param packageName package to check. - * @return true if the user shouldn't be able to uninstall the package. + * @return true if uninstallation is blocked. */ public boolean isUninstallBlocked(ComponentName admin, String packageName) { if (mService != null) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 07aa800..d144ae8 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -66,6 +66,7 @@ interface IDevicePolicyManager { boolean isActivePasswordSufficient(int userHandle); int getCurrentFailedPasswordAttempts(int userHandle); + int getProfileWithMinimumFailedPasswordsForWipe(int userHandle); void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle); int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle); diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 0a2d4f5..41ad936 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -303,4 +303,11 @@ interface IBackupManager { * {@code false} otherwise. */ void setBackupServiceActive(int whichUser, boolean makeActive); + + /** + * Queries the activity status of backup service as set by {@link #setBackupServiceActive}. + * @param whichUser User handle of the defined user whose backup active state + * is being queried. + */ + boolean isBackupServiceActive(int whichUser); } diff --git a/core/java/android/app/backup/RecentsBackupHelper.java b/core/java/android/app/backup/RecentsBackupHelper.java new file mode 100644 index 0000000..fd69d20 --- /dev/null +++ b/core/java/android/app/backup/RecentsBackupHelper.java @@ -0,0 +1,114 @@ +package android.app.backup; + +import android.content.Context; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.util.Slog; + +import java.io.File; + +/** + * Helper for saving/restoring 'recent tasks' infrastructure. + * @hide + */ +public class RecentsBackupHelper implements BackupHelper { + private static final String TAG = "RecentsBackup"; + private static final boolean DEBUG = false; + + // This must match TaskPersister.TASKS_DIRNAME, but that class is not accessible from here + private static final String RECENTS_TASK_DIR = "recent_tasks"; + + // Must match TaskPersister.IMAGES_DIRNAME, as above + private static final String RECENTS_IMAGE_DIR = "recent_images"; + + // At restore time, tasks/thumbnails are placed in these directories alongside + // the "live" recents dirs named above. + private static final String RECENTS_TASK_RESTORE_DIR = "restored_" + RECENTS_TASK_DIR; + private static final String RECENTS_IMAGE_RESTORE_DIR = "restored_" + RECENTS_IMAGE_DIR; + + // Prefixes for tagging the two kinds of recents backup records that we might generate + private static final String RECENTS_TASK_KEY = "task:"; + private static final String RECENTS_IMAGE_KEY = "image:"; + + FileBackupHelperBase mTaskFileHelper; + + final File mSystemDir; + final File mTasksDir; + final File mRestoredTasksDir; + final File mRestoredImagesDir; + final String[] mRecentFiles; + final String[] mRecentKeys; + + /** + * @param context The agent context in which this helper instance will run + */ + public RecentsBackupHelper(Context context) { + mTaskFileHelper = new FileBackupHelperBase(context); + + mSystemDir = new File(Environment.getDataDirectory(), "system"); + mTasksDir = new File(mSystemDir, RECENTS_TASK_DIR); + mRestoredTasksDir = new File(mSystemDir, RECENTS_TASK_RESTORE_DIR); + mRestoredImagesDir = new File(mSystemDir, RECENTS_IMAGE_RESTORE_DIR); + + // Currently we back up only the recent-task descriptions, not the thumbnails + File[] recentFiles = mTasksDir.listFiles(); + if (recentFiles != null) { + // We explicitly proceed even if this is a zero-size array + final int N = recentFiles.length; + mRecentKeys = new String[N]; + mRecentFiles = new String[N]; + if (DEBUG) { + Slog.i(TAG, "Identifying recents for backup: " + N); + } + for (int i = 0; i < N; i++) { + mRecentKeys[i] = new String(RECENTS_TASK_KEY + recentFiles[i].getName()); + mRecentFiles[i] = recentFiles[i].getAbsolutePath(); + if (DEBUG) { + Slog.i(TAG, " " + mRecentKeys[i]); + } + } + } else { + mRecentFiles = mRecentKeys = new String[0]; + } + } + + /** + * Task-file key: RECENTS_TASK_KEY + leaf filename + * Thumbnail-file key: RECENTS_IMAGE_KEY + leaf filename + */ + @Override + public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) { + FileBackupHelperBase.performBackup_checked(oldState, data, newState, + mRecentFiles, mRecentKeys); + } + + @Override + public void restoreEntity(BackupDataInputStream data) { + final String key = data.getKey(); + File output = null; + if (key.startsWith(RECENTS_TASK_KEY)) { + String name = key.substring(RECENTS_TASK_KEY.length()); + output = new File(mRestoredTasksDir, name); + mRestoredTasksDir.mkdirs(); + } else if (key.startsWith(RECENTS_IMAGE_KEY)) { + String name = key.substring(RECENTS_IMAGE_KEY.length()); + output = new File(mRestoredImagesDir, name); + mRestoredImagesDir.mkdirs(); + } + + if (output != null) { + if (DEBUG) { + Slog.i(TAG, "Restoring key='" + + key + "' to " + output.getAbsolutePath()); + } + mTaskFileHelper.writeFile(output, data); + } + } + + @Override + public void writeNewStateDescription(ParcelFileDescriptor newState) { + mTaskFileHelper.writeNewStateDescription(newState); + } + +} diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 0193711..68ea0aa 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -29,5 +29,6 @@ interface ITrustManager { void reportRequireCredentialEntry(int userId); void registerTrustListener(in ITrustListener trustListener); void unregisterTrustListener(in ITrustListener trustListener); - boolean isTrusted(int userId); + void reportKeyguardShowingChanged(); + boolean isDeviceLocked(int userId); } diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 3d262b1..705a144 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -88,6 +88,19 @@ public class TrustManager { } /** + * Reports that the visibility of the keyguard has changed. + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + */ + public void reportKeyguardShowingChanged() { + try { + mService.reportKeyguardShowingChanged(); + } catch (RemoteException e) { + onError(e); + } + } + + /** * Registers a listener for trust events. * * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission. diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java index a019d5c..e76c23b 100644 --- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -117,8 +117,9 @@ public final class BluetoothLeAdvertiser { AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED); return; } - if (totalBytes(advertiseData) > MAX_ADVERTISING_DATA_BYTES || - totalBytes(scanResponse) > MAX_ADVERTISING_DATA_BYTES) { + boolean isConnectable = settings.isConnectable(); + if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES || + totalBytes(scanResponse, isConnectable) > MAX_ADVERTISING_DATA_BYTES) { postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); return; } @@ -171,9 +172,10 @@ public final class BluetoothLeAdvertiser { } // Compute the size of the advertise data. - private int totalBytes(AdvertiseData data) { + private int totalBytes(AdvertiseData data, boolean isConnectable) { if (data == null) return 0; - int size = FLAGS_FIELD_BYTES; // flags field is always set. + // Flags field is omitted if the advertising is not connectable. + int size = isConnectable ? FLAGS_FIELD_BYTES : 0; if (data.getServiceUuids() != null) { int num16BitUuids = 0; int num32BitUuids = 0; diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index c8f9b7d..360f308 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -31,6 +31,7 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.IBinder; import android.os.ICancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; @@ -201,7 +202,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { ICancellationSignal cancellationSignal) { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return rejectQuery(uri, projection, selection, selectionArgs, sortOrder, CancellationSignal.fromTransport(cancellationSignal)); } @@ -227,7 +228,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = getUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return rejectInsert(uri, initialValues); } final String original = setCallingPackage(callingPkg); @@ -242,7 +243,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return 0; } final String original = setCallingPackage(callingPkg); @@ -270,13 +271,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 { operations.set(i, operation); } if (operation.isReadOperation()) { - if (enforceReadPermission(callingPkg, uri) + if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { throw new OperationApplicationException("App op not allowed", 0); } } if (operation.isWriteOperation()) { - if (enforceWritePermission(callingPkg, uri) + if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { throw new OperationApplicationException("App op not allowed", 0); } @@ -301,7 +302,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return 0; } final String original = setCallingPackage(callingPkg); @@ -317,7 +318,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { String[] selectionArgs) { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return 0; } final String original = setCallingPackage(callingPkg); @@ -330,11 +331,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public ParcelFileDescriptor openFile( - String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal) - throws FileNotFoundException { + String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal, + IBinder callerToken) throws FileNotFoundException { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - enforceFilePermission(callingPkg, uri, mode); + enforceFilePermission(callingPkg, uri, mode, callerToken); final String original = setCallingPackage(callingPkg); try { return ContentProvider.this.openFile( @@ -350,7 +351,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { throws FileNotFoundException { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - enforceFilePermission(callingPkg, uri, mode); + enforceFilePermission(callingPkg, uri, mode, null); final String original = setCallingPackage(callingPkg); try { return ContentProvider.this.openAssetFile( @@ -382,7 +383,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - enforceFilePermission(callingPkg, uri, "r"); + enforceFilePermission(callingPkg, uri, "r", null); final String original = setCallingPackage(callingPkg); try { return ContentProvider.this.openTypedAssetFile( @@ -402,7 +403,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = getUriWithoutUserId(uri); - if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return null; } final String original = setCallingPackage(callingPkg); @@ -418,7 +419,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = getUriWithoutUserId(uri); - if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { return null; } final String original = setCallingPackage(callingPkg); @@ -429,29 +430,33 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } } - private void enforceFilePermission(String callingPkg, Uri uri, String mode) - throws FileNotFoundException, SecurityException { + private void enforceFilePermission(String callingPkg, Uri uri, String mode, + IBinder callerToken) throws FileNotFoundException, SecurityException { if (mode != null && mode.indexOf('w') != -1) { - if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(callingPkg, uri, callerToken) + != AppOpsManager.MODE_ALLOWED) { throw new FileNotFoundException("App op not allowed"); } } else { - if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(callingPkg, uri, callerToken) + != AppOpsManager.MODE_ALLOWED) { throw new FileNotFoundException("App op not allowed"); } } } - private int enforceReadPermission(String callingPkg, Uri uri) throws SecurityException { - enforceReadPermissionInner(uri); + private int enforceReadPermission(String callingPkg, Uri uri, IBinder callerToken) + throws SecurityException { + enforceReadPermissionInner(uri, callerToken); if (mReadOp != AppOpsManager.OP_NONE) { return mAppOpsManager.noteOp(mReadOp, Binder.getCallingUid(), callingPkg); } return AppOpsManager.MODE_ALLOWED; } - private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException { - enforceWritePermissionInner(uri); + private int enforceWritePermission(String callingPkg, Uri uri, IBinder callerToken) + throws SecurityException { + enforceWritePermissionInner(uri, callerToken); if (mWriteOp != AppOpsManager.OP_NONE) { return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg); } @@ -467,7 +472,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** {@hide} */ - protected void enforceReadPermissionInner(Uri uri) throws SecurityException { + protected void enforceReadPermissionInner(Uri uri, IBinder callerToken) + throws SecurityException { final Context context = getContext(); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); @@ -480,7 +486,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { if (mExported && checkUser(pid, uid, context)) { final String componentPerm = getReadPermission(); if (componentPerm != null) { - if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { + if (context.checkPermission(componentPerm, pid, uid, callerToken) + == PERMISSION_GRANTED) { return; } else { missingPerm = componentPerm; @@ -497,7 +504,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { for (PathPermission pp : pps) { final String pathPerm = pp.getReadPermission(); if (pathPerm != null && pp.match(path)) { - if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { + if (context.checkPermission(pathPerm, pid, uid, callerToken) + == PERMISSION_GRANTED) { return; } else { // any denied <path-permission> means we lose @@ -518,8 +526,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { final int callingUserId = UserHandle.getUserId(uid); final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid)) ? maybeAddUserId(uri, callingUserId) : uri; - if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) - == PERMISSION_GRANTED) { + if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION, + callerToken) == PERMISSION_GRANTED) { return; } @@ -532,7 +540,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** {@hide} */ - protected void enforceWritePermissionInner(Uri uri) throws SecurityException { + protected void enforceWritePermissionInner(Uri uri, IBinder callerToken) + throws SecurityException { final Context context = getContext(); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); @@ -545,7 +554,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { if (mExported && checkUser(pid, uid, context)) { final String componentPerm = getWritePermission(); if (componentPerm != null) { - if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { + if (context.checkPermission(componentPerm, pid, uid, callerToken) + == PERMISSION_GRANTED) { return; } else { missingPerm = componentPerm; @@ -562,7 +572,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { for (PathPermission pp : pps) { final String pathPerm = pp.getWritePermission(); if (pathPerm != null && pp.match(path)) { - if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { + if (context.checkPermission(pathPerm, pid, uid, callerToken) + == PERMISSION_GRANTED) { return; } else { // any denied <path-permission> means we lose @@ -580,8 +591,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } // last chance, check against any uri grants - if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - == PERMISSION_GRANTED) { + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + callerToken) == PERMISSION_GRANTED) { return; } @@ -1659,12 +1670,6 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } private void attachInfo(Context context, ProviderInfo info, boolean testing) { - /* - * We may be using AsyncTask from binder threads. Make it init here - * so its static handler is on the main thread. - */ - AsyncTask.init(); - mNoPerms = testing; /* diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index cefc27f..e15ac94 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -288,7 +288,7 @@ public class ContentProviderClient { remoteSignal = mContentProvider.createCancellationSignal(); signal.setRemote(remoteSignal); } - return mContentProvider.openFile(mPackageName, url, mode, remoteSignal); + return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 39286d6..f2e7fc4 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -234,9 +234,10 @@ abstract public class ContentProviderNative extends Binder implements IContentPr String mode = data.readString(); ICancellationSignal signal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); + IBinder callerToken = data.readStrongBinder(); ParcelFileDescriptor fd; - fd = openFile(callingPkg, url, mode, signal); + fd = openFile(callingPkg, url, mode, signal, callerToken); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -575,7 +576,7 @@ final class ContentProviderProxy implements IContentProvider @Override public ParcelFileDescriptor openFile( - String callingPkg, Uri url, String mode, ICancellationSignal signal) + String callingPkg, Uri url, String mode, ICancellationSignal signal, IBinder token) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -586,6 +587,7 @@ final class ContentProviderProxy implements IContentProvider url.writeToParcel(data, 0); data.writeString(mode); data.writeStrongBinder(signal != null ? signal.asBinder() : null); + data.writeStrongBinder(token); mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index c9b7d0a..e6bb09f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -37,6 +37,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.StatFs; import android.os.UserHandle; @@ -2244,6 +2245,8 @@ public abstract class Context { * @see android.media.MediaRouter * @see #TELEPHONY_SERVICE * @see android.telephony.TelephonyManager + * @see #TELEPHONY_SUBSCRIPTION_SERVICE + * @see android.telephony.SubscriptionManager * @see #INPUT_METHOD_SERVICE * @see android.view.inputmethod.InputMethodManager * @see #UI_MODE_SERVICE @@ -2588,6 +2591,16 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.telephony.SubscriptionManager} for handling management the + * telephony subscriptions of the device. + * + * @see #getSystemService + * @see android.telephony.SubscriptionManager + */ + public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.telecom.TelecomManager} to manage telecom-related features * of the device. * @@ -2864,11 +2877,10 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a {@link - * android.app.UsageStatsManager} for interacting with the status bar. + * android.app.usage.UsageStatsManager} for querying device usage stats. * * @see #getSystemService - * @see android.app.UsageStatsManager - * @hide + * @see android.app.usage.UsageStatsManager */ public static final String USAGE_STATS_SERVICE = "usagestats"; @@ -2921,6 +2933,11 @@ public abstract class Context { @PackageManager.PermissionResult public abstract int checkPermission(@NonNull String permission, int pid, int uid); + /** @hide */ + @PackageManager.PermissionResult + public abstract int checkPermission(@NonNull String permission, int pid, int uid, + IBinder callerToken); + /** * Determine whether the calling process of an IPC you are handling has been * granted a particular permission. This is basically the same as calling @@ -3108,6 +3125,10 @@ public abstract class Context { public abstract int checkUriPermission(Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags); + /** @hide */ + public abstract int checkUriPermission(Uri uri, int pid, int uid, + @Intent.AccessUriMode int modeFlags, IBinder callerToken); + /** * Determine whether the calling process and user ID has been * granted permission to access a specific URI. This is basically diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index ad7c350..cfae1cf 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -29,6 +29,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.view.DisplayAdjustments; @@ -566,6 +567,12 @@ public class ContextWrapper extends Context { return mBase.checkPermission(permission, pid, uid); } + /** @hide */ + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + return mBase.checkPermission(permission, pid, uid, callerToken); + } + @Override public int checkCallingPermission(String permission) { return mBase.checkCallingPermission(permission); @@ -608,6 +615,12 @@ public class ContextWrapper extends Context { return mBase.checkUriPermission(uri, pid, uid, modeFlags); } + /** @hide */ + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { + return mBase.checkUriPermission(uri, pid, uid, modeFlags, callerToken); + } + @Override public int checkCallingUriPermission(Uri uri, int modeFlags) { return mBase.checkCallingUriPermission(uri, modeFlags); diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index f92a404..f858406 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -47,7 +47,8 @@ public interface IContentProvider extends IInterface { public int update(String callingPkg, Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException; public ParcelFileDescriptor openFile( - String callingPkg, Uri url, String mode, ICancellationSignal signal) + String callingPkg, Uri url, String mode, ICancellationSignal signal, + IBinder callerToken) throws RemoteException, FileNotFoundException; public AssetFileDescriptor openAssetFile( String callingPkg, Uri url, String mode, ICancellationSignal signal) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 57f6028..de7fbab 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3763,7 +3763,7 @@ public class Intent implements Parcelable, Cloneable { * This flag is used to open a document into a new task rooted at the activity launched * by this Intent. Through the use of this flag, or its equivalent attribute, * {@link android.R.attr#documentLaunchMode} multiple instances of the same activity - * containing different douments will appear in the recent tasks list. + * containing different documents will appear in the recent tasks list. * * <p>The use of the activity attribute form of this, * {@link android.R.attr#documentLaunchMode}, is @@ -4010,6 +4010,20 @@ public class Intent implements Parcelable, Cloneable { */ public static final int URI_ANDROID_APP_SCHEME = 1<<1; + /** + * Flag for use with {@link #toUri} and {@link #parseUri}: allow parsing + * of unsafe information. In particular, the flags {@link #FLAG_GRANT_READ_URI_PERMISSION}, + * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, {@link #FLAG_GRANT_PERSISTABLE_URI_PERMISSION}, + * and {@link #FLAG_GRANT_PREFIX_URI_PERMISSION} flags can not be set, so that the + * generated Intent can not cause unexpected data access to happen. + * + * <p>If you do not trust the source of the URI being parsed, you should still do further + * processing to protect yourself from it. In particular, when using it to start an + * activity you should usually add in {@link #CATEGORY_BROWSABLE} to limit the activities + * that can handle it.</p> + */ + public static final int URI_ALLOW_UNSAFE = 1<<2; + // --------------------------------------------------------------------- private String mAction; @@ -4309,7 +4323,7 @@ public class Intent implements Parcelable, Cloneable { // old format Intent URI } else if (!uri.startsWith("#Intent;", i)) { if (!androidApp) { - return getIntentOld(uri); + return getIntentOld(uri, flags); } else { i = -1; } @@ -4359,6 +4373,9 @@ public class Intent implements Parcelable, Cloneable { // launch flags else if (uri.startsWith("launchFlags=", i)) { intent.mFlags = Integer.decode(value).intValue(); + if ((flags& URI_ALLOW_UNSAFE) == 0) { + intent.mFlags &= ~IMMUTABLE_FLAGS; + } } // package @@ -4488,6 +4505,10 @@ public class Intent implements Parcelable, Cloneable { } public static Intent getIntentOld(String uri) throws URISyntaxException { + return getIntentOld(uri, 0); + } + + private static Intent getIntentOld(String uri, int flags) throws URISyntaxException { Intent intent; int i = uri.lastIndexOf('#'); @@ -4536,6 +4557,9 @@ public class Intent implements Parcelable, Cloneable { i += 12; int j = uri.indexOf(')', i); intent.mFlags = Integer.decode(uri.substring(i, j)).intValue(); + if ((flags& URI_ALLOW_UNSAFE) == 0) { + intent.mFlags &= ~IMMUTABLE_FLAGS; + } i = j + 1; } diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index b66bd01..9223269 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -41,14 +41,30 @@ public class PackageInfo implements Parcelable { * attribute. */ public int versionCode; - + /** * The version name of this package, as specified by the <manifest> * tag's {@link android.R.styleable#AndroidManifest_versionName versionName} * attribute. */ public String versionName; - + + /** + * The revision number of the base APK for this package, as specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. + */ + public int baseRevisionCode; + + /** + * The revision number of any split APKs for this package, as specified by + * the <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. Indexes are a 1:1 mapping against {@link #splitNames}. + */ + public int[] splitRevisionCodes; + /** * The shared user ID name of this package, as specified by the <manifest> * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId} @@ -257,21 +273,26 @@ public class PackageInfo implements Parcelable { public PackageInfo() { } + @Override public String toString() { return "PackageInfo{" + Integer.toHexString(System.identityHashCode(this)) + " " + packageName + "}"; } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(packageName); dest.writeStringArray(splitNames); dest.writeInt(versionCode); dest.writeString(versionName); + dest.writeInt(baseRevisionCode); + dest.writeIntArray(splitRevisionCodes); dest.writeString(sharedUserId); dest.writeInt(sharedUserLabel); if (applicationInfo != null) { @@ -305,10 +326,12 @@ public class PackageInfo implements Parcelable { public static final Parcelable.Creator<PackageInfo> CREATOR = new Parcelable.Creator<PackageInfo>() { + @Override public PackageInfo createFromParcel(Parcel source) { return new PackageInfo(source); } + @Override public PackageInfo[] newArray(int size) { return new PackageInfo[size]; } @@ -316,9 +339,11 @@ public class PackageInfo implements Parcelable { private PackageInfo(Parcel source) { packageName = source.readString(); - splitNames = source.readStringArray(); + splitNames = source.createStringArray(); versionCode = source.readInt(); versionName = source.readString(); + baseRevisionCode = source.readInt(); + splitRevisionCodes = source.createIntArray(); sharedUserId = source.readString(); sharedUserLabel = source.readInt(); int hasApp = source.readInt(); diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java index e336c5f..1efe082 100644 --- a/core/java/android/content/pm/PackageInfoLite.java +++ b/core/java/android/content/pm/PackageInfoLite.java @@ -19,6 +19,8 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.content.PackageHelper; + /** * Basic information about a package as specified in its manifest. * Utility class used in PackageManager methods @@ -31,11 +33,19 @@ public class PackageInfoLite implements Parcelable { */ public String packageName; + /** Names of any split APKs, ordered by parsed splitName */ + public String[] splitNames; + /** * The android:versionCode of the package. */ public int versionCode; + /** Revision code of base APK */ + public int baseRevisionCode; + /** Revision codes of any split APKs, ordered by parsed splitName */ + public int[] splitRevisionCodes; + /** * The android:multiArch flag from the package manifest. If set, * we will extract all native libraries for the given app, not just those @@ -70,7 +80,10 @@ public class PackageInfoLite implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(packageName); + dest.writeStringArray(splitNames); dest.writeInt(versionCode); + dest.writeInt(baseRevisionCode); + dest.writeIntArray(splitRevisionCodes); dest.writeInt(recommendedInstallLocation); dest.writeInt(installLocation); dest.writeInt(multiArch ? 1 : 0); @@ -96,7 +109,10 @@ public class PackageInfoLite implements Parcelable { private PackageInfoLite(Parcel source) { packageName = source.readString(); + splitNames = source.createStringArray(); versionCode = source.readInt(); + baseRevisionCode = source.readInt(); + splitRevisionCodes = source.createIntArray(); recommendedInstallLocation = source.readInt(); installLocation = source.readInt(); multiArch = (source.readInt() != 0); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e9f7c50..5c705e6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1078,6 +1078,26 @@ public abstract class PackageManager { "android.hardware.camera.capability.raw"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one + * of the cameras on the device supports the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE} + * capability level. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_CAPABILITY_BURST_CAPTURE = + "android.hardware.camera.capability.burst_capture"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one + * of the cameras on the device supports the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS READ_SENSOR_SETTINGS} + * capability level. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_CAPABILITY_READ_SENSOR_SETTINGS = + "android.hardware.camera.capability.read_sensor_settings"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device is capable of communicating with * consumer IR devices. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 8515520..82da7c5 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -261,11 +261,16 @@ public class PackageParser { /** Paths of any split APKs, ordered by parsed splitName */ public final String[] splitCodePaths; + /** Revision code of base APK */ + public final int baseRevisionCode; + /** Revision codes of any split APKs, ordered by parsed splitName */ + public final int[] splitRevisionCodes; + public final boolean coreApp; public final boolean multiArch; public PackageLite(String codePath, ApkLite baseApk, String[] splitNames, - String[] splitCodePaths) { + String[] splitCodePaths, int[] splitRevisionCodes) { this.packageName = baseApk.packageName; this.versionCode = baseApk.versionCode; this.installLocation = baseApk.installLocation; @@ -274,6 +279,8 @@ public class PackageParser { this.codePath = codePath; this.baseCodePath = baseApk.codePath; this.splitCodePaths = splitCodePaths; + this.baseRevisionCode = baseApk.revisionCode; + this.splitRevisionCodes = splitRevisionCodes; this.coreApp = baseApk.coreApp; this.multiArch = baseApk.multiArch; } @@ -296,6 +303,7 @@ public class PackageParser { public final String packageName; public final String splitName; public final int versionCode; + public final int revisionCode; public final int installLocation; public final VerifierInfo[] verifiers; public final Signature[] signatures; @@ -303,12 +311,13 @@ public class PackageParser { public final boolean multiArch; public ApkLite(String codePath, String packageName, String splitName, int versionCode, - int installLocation, List<VerifierInfo> verifiers, Signature[] signatures, - boolean coreApp, boolean multiArch) { + int revisionCode, int installLocation, List<VerifierInfo> verifiers, + Signature[] signatures, boolean coreApp, boolean multiArch) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; this.versionCode = versionCode; + this.revisionCode = revisionCode; this.installLocation = installLocation; this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); this.signatures = signatures; @@ -409,6 +418,8 @@ public class PackageParser { pi.packageName = p.packageName; pi.splitNames = p.splitNames; pi.versionCode = p.mVersionCode; + pi.baseRevisionCode = p.baseRevisionCode; + pi.splitRevisionCodes = p.splitRevisionCodes; pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; @@ -647,7 +658,7 @@ public class PackageParser { throws PackageParserException { final ApkLite baseApk = parseApkLite(packageFile, flags); final String packagePath = packageFile.getAbsolutePath(); - return new PackageLite(packagePath, baseApk, null, null); + return new PackageLite(packagePath, baseApk, null, null, null); } private static PackageLite parseClusterPackageLite(File packageDir, int flags) @@ -704,20 +715,24 @@ public class PackageParser { String[] splitNames = null; String[] splitCodePaths = null; + int[] splitRevisionCodes = null; if (size > 0) { splitNames = new String[size]; splitCodePaths = new String[size]; + splitRevisionCodes = new int[size]; splitNames = apks.keySet().toArray(splitNames); Arrays.sort(splitNames, sSplitNameComparator); for (int i = 0; i < size; i++) { splitCodePaths[i] = apks.get(splitNames[i]).codePath; + splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode; } } final String codePath = packageDir.getAbsolutePath(); - return new PackageLite(codePath, baseApk, splitNames, splitCodePaths); + return new PackageLite(codePath, baseApk, splitNames, splitCodePaths, + splitRevisionCodes); } /** @@ -782,6 +797,7 @@ public class PackageParser { final int num = lite.splitNames.length; pkg.splitNames = lite.splitNames; pkg.splitCodePaths = lite.splitCodePaths; + pkg.splitRevisionCodes = lite.splitRevisionCodes; pkg.splitFlags = new int[num]; for (int i = 0; i < num; i++) { @@ -1249,25 +1265,21 @@ public class PackageParser { int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; int versionCode = 0; + int revisionCode = 0; boolean coreApp = false; boolean multiArch = false; - int numFound = 0; for (int i = 0; i < attrs.getAttributeCount(); i++) { - String attr = attrs.getAttributeName(i); + final String attr = attrs.getAttributeName(i); if (attr.equals("installLocation")) { installLocation = attrs.getAttributeIntValue(i, PARSE_DEFAULT_INSTALL_LOCATION); - numFound++; } else if (attr.equals("versionCode")) { versionCode = attrs.getAttributeIntValue(i, 0); - numFound++; + } else if (attr.equals("revisionCode")) { + revisionCode = attrs.getAttributeIntValue(i, 0); } else if (attr.equals("coreApp")) { coreApp = attrs.getAttributeBooleanValue(i, false); - numFound++; - } - if (numFound >= 3) { - break; } } @@ -1301,7 +1313,7 @@ public class PackageParser { } return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, - installLocation, verifiers, signatures, coreApp, multiArch); + revisionCode, installLocation, verifiers, signatures, coreApp, multiArch); } /** @@ -1359,6 +1371,8 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifest); pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_versionCode, 0); + pkg.baseRevisionCode = sa.getInteger( + com.android.internal.R.styleable.AndroidManifest_revisionCode, 0); pkg.mVersionName = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifest_versionName, 0); if (pkg.mVersionName != null) { @@ -4168,6 +4182,7 @@ public class PackageParser { public final static class Package { public String packageName; + /** Names of any split APKs, ordered by parsed splitName */ public String[] splitNames; @@ -4185,6 +4200,11 @@ public class PackageParser { /** Paths of any split APKs, ordered by parsed splitName */ public String[] splitCodePaths; + /** Revision code of base APK */ + public int baseRevisionCode; + /** Revision codes of any split APKs, ordered by parsed splitName */ + public int[] splitRevisionCodes; + /** Flags of any split APKs; ordered by parsed splitName */ public int[] splitFlags; @@ -4222,7 +4242,7 @@ public class PackageParser { // The version code declared for this package. public int mVersionCode; - + // The version name declared for this package. public String mVersionName; diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java index 8a43472..335a45e 100644 --- a/core/java/android/content/pm/ParceledListSlice.java +++ b/core/java/android/content/pm/ParceledListSlice.java @@ -30,6 +30,12 @@ import java.util.List; * Transfer a large list of Parcelable objects across an IPC. Splits into * multiple transactions if needed. * + * Caveat: for efficiency and security, all elements must be the same concrete type. + * In order to avoid writing the class name of each object, we must ensure that + * each object is the same type, or else unparceling then reparceling the data may yield + * a different result if the class name encoded in the Parcelable is a Base type. + * See b/17671747. + * * @hide */ public class ParceledListSlice<T extends Parcelable> implements Parcelable { @@ -56,13 +62,25 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { if (N <= 0) { return; } + Parcelable.Creator<T> creator = p.readParcelableCreator(loader); + Class<?> listElementClass = null; + int i = 0; while (i < N) { if (p.readInt() == 0) { break; } - mList.add(p.readCreator(creator, loader)); + + final T parcelable = p.readCreator(creator, loader); + if (listElementClass == null) { + listElementClass = parcelable.getClass(); + } else { + verifySameType(listElementClass, parcelable.getClass()); + } + + mList.add(parcelable); + if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); i++; } @@ -82,7 +100,11 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { return; } while (i < N && reply.readInt() != 0) { - mList.add(reply.readCreator(creator, loader)); + final T parcelable = reply.readCreator(creator, loader); + verifySameType(listElementClass, parcelable.getClass()); + + mList.add(parcelable); + if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); i++; } @@ -91,6 +113,14 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { } } + private static void verifySameType(final Class<?> expected, final Class<?> actual) { + if (!actual.equals(expected)) { + throw new IllegalArgumentException("Can't unparcel type " + + actual.getName() + " in list of type " + + expected.getName()); + } + } + public List<T> getList() { return mList; } @@ -116,11 +146,16 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { dest.writeInt(N); if (DEBUG) Log.d(TAG, "Writing " + N + " items"); if (N > 0) { + final Class<?> listElementClass = mList.get(0).getClass(); dest.writeParcelableCreator(mList.get(0)); int i = 0; while (i < N && dest.dataSize() < MAX_FIRST_IPC_SIZE) { dest.writeInt(1); - mList.get(i).writeToParcel(dest, callFlags); + + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + parcelable.writeToParcel(dest, callFlags); + if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); i++; } @@ -137,7 +172,11 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); while (i < N && reply.dataSize() < MAX_IPC_SIZE) { reply.writeInt(1); - mList.get(i).writeToParcel(reply, callFlags); + + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + parcelable.writeToParcel(reply, callFlags); + if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); i++; } diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index e578822..ecae52c 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -666,6 +666,14 @@ public final class AssetManager implements AutoCloseable { /** * Get the locales that this asset manager contains data for. + * + * <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid + * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be + * parsed using {@link java.util.Locale#forLanguageTag(String)}. + * + * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings + * are of the form {@code ll_CC} where {@code ll} is a two letter language code, + * and {@code CC} is a two letter country code. */ public native final String[] getLocales(); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 0145e05..78d3e9c 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -747,7 +747,7 @@ public class Resources { */ public Drawable getDrawable(int id) throws NotFoundException { final Drawable d = getDrawable(id, null); - if (d.canApplyTheme()) { + if (d != null && d.canApplyTheme()) { Log.w(TAG, "Drawable " + getResourceName(id) + " has unresolved theme " + "attributes! Consider using Resources.getDrawable(int, Theme) or " + "Context.getDrawable(int).", new RuntimeException()); diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java index 77b8a33..9652db7 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -311,13 +311,13 @@ final class StringBlock { * the color black is returned instead. * * @param color The color as a string. Can be a resource reference, - * HTML hexadecimal, octal or a name + * hexadecimal, octal or a name * @param foreground True if the color will be used as the foreground color, * false otherwise * * @return A CharacterStyle * - * @see Color#getHtmlColor(String) + * @see Color#parseColor(String) */ private static CharacterStyle getColor(String color, boolean foreground) { int c = 0xff000000; @@ -336,7 +336,11 @@ final class StringBlock { } } } else { - c = Color.getHtmlColor(color); + try { + c = Color.parseColor(color); + } catch (IllegalArgumentException e) { + c = Color.BLACK; + } } } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index fb3eaff..98096dc 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -395,6 +395,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p><b>Range of valid values:</b><br></p> * <p><code>Min.exposure compensation * {@link CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP android.control.aeCompensationStep} <= -2 EV</code></p> * <p><code>Max.exposure compensation * {@link CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP android.control.aeCompensationStep} >= 2 EV</code></p> + * <p>LEGACY devices may support a smaller range than this, including the range [0,0], which + * indicates that changing the exposure compensation is not supported.</p> * <p>This key is available on all devices.</p> * * @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP @@ -1155,6 +1157,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING MANUAL_POST_PROCESSING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_RAW RAW}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS READ_SENSOR_SETTINGS}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -1163,6 +1167,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_RAW + * @see #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS + * @see #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE */ @PublicKey public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = @@ -2283,12 +2289,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Camera devices will come in three flavors: LEGACY, LIMITED and FULL.</p> * <p>A FULL device will support below capabilities:</p> * <ul> - * <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li> + * <li>30fps operation at maximum resolution (== sensor resolution) is preferred, more than + * 20fps is required, for at least uncompressed YUV + * output. ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains BURST_CAPTURE)</li> * <li>Per frame control ({@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} <code>==</code> PER_FRAME_CONTROL)</li> * <li>Manual sensor control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR)</li> - * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_POST_PROCESSING)</li> + * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains + * MANUAL_POST_PROCESSING)</li> * <li>Arbitrary cropping region ({@link CameraCharacteristics#SCALER_CROPPING_TYPE android.scaler.croppingType} <code>==</code> FREEFORM)</li> - * <li>At least 3 processed (but not stalling) format output streams ({@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC android.request.maxNumOutputProc} <code>>=</code> 3)</li> + * <li>At least 3 processed (but not stalling) format output streams + * ({@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC android.request.maxNumOutputProc} <code>>=</code> 3)</li> * <li>The required stream configuration defined in android.scaler.availableStreamConfigurations</li> * <li>The required exposure time range defined in {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li> * <li>The required maxFrameDuration defined in {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li> diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 0bb742c..9e90d01 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -334,6 +334,24 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * + * <p>BURST-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes + * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}) devices + * support at least the below stream combinations in addition to those for + * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices. Note that all + * FULL-level devices support the BURST capability, and the below list is a strict subset of the + * list for FULL-level devices, so this table is only relevant for LIMITED-level devices that + * support the BURST_CAPTURE capability. + * + * <table> + * <tr><th colspan="5">BURST-capability additional guaranteed configurations</th></tr> + * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution GPU processing with preview.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution in-app processing with preview.</td> </tr> + * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processsing.</td> </tr> + * </table><br> + * </p> + * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for * by attempting to create a session with such targets.</p> diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 9a9e4c2..b6bb33b 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -54,29 +54,17 @@ public final class CameraManager { private static final String TAG = "CameraManager"; private final boolean DEBUG; - /** - * This should match the ICameraService definition - */ - private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; private static final int USE_CALLING_UID = -1; @SuppressWarnings("unused") private static final int API_VERSION_1 = 1; private static final int API_VERSION_2 = 2; - // Access only through getCameraServiceLocked to deal with binder death - private ICameraService mCameraService; - private ArrayList<String> mDeviceIdList; - private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap = - new ArrayMap<AvailabilityCallback, Handler>(); - private final Context mContext; private final Object mLock = new Object(); - private final CameraServiceListener mServiceListener = new CameraServiceListener(); - /** * @hide */ @@ -84,8 +72,6 @@ public final class CameraManager { DEBUG = Log.isLoggable(TAG, Log.DEBUG); synchronized(mLock) { mContext = context; - - connectCameraServiceLocked(); } } @@ -116,6 +102,12 @@ public final class CameraManager { * <p>The first time a callback is registered, it is immediately called * with the availability status of all currently known camera devices.</p> * + * <p>Since this callback will be registered with the camera service, remember to unregister it + * once it is no longer needed; otherwise the callback will continue to receive events + * indefinitely and it may prevent other resources from being released. Specifically, the + * callbacks will be invoked independently of the general activity lifecycle and independently + * of the state of individual CameraManager instances.</p> + * * @param callback the new callback to send camera availability notices to * @param handler The handler on which the callback should be invoked, or * {@code null} to use the current thread's {@link android.os.Looper looper}. @@ -130,13 +122,7 @@ public final class CameraManager { handler = new Handler(looper); } - synchronized (mLock) { - Handler oldHandler = mCallbackMap.put(callback, handler); - // For new callbacks, provide initial availability information - if (oldHandler == null) { - mServiceListener.updateCallbackLocked(callback, handler); - } - } + CameraManagerGlobal.get().registerAvailabilityCallback(callback, handler); } /** @@ -148,9 +134,7 @@ public final class CameraManager { * @param callback The callback to remove from the notification list */ public void unregisterAvailabilityCallback(AvailabilityCallback callback) { - synchronized (mLock) { - mCallbackMap.remove(callback); - } + CameraManagerGlobal.get().unregisterAvailabilityCallback(callback); } /** @@ -187,7 +171,7 @@ public final class CameraManager { * otherwise get them from the legacy shim instead. */ - ICameraService cameraService = getCameraServiceLocked(); + ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); if (cameraService == null) { throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, "Camera service is currently unavailable"); @@ -268,7 +252,7 @@ public final class CameraManager { try { if (supportsCamera2ApiLocked(cameraId)) { // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices - ICameraService cameraService = getCameraServiceLocked(); + ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); if (cameraService == null) { throw new CameraRuntimeException( CameraAccessException.CAMERA_DISCONNECTED, @@ -444,13 +428,6 @@ public final class CameraManager { } /** - * Temporary for migrating to Callback naming - * @hide - */ - public static abstract class AvailabilityListener extends AvailabilityCallback { - } - - /** * Return or create the list of currently connected camera devices. * * <p>In case of errors connecting to the camera service, will return an empty list.</p> @@ -458,7 +435,7 @@ public final class CameraManager { private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException { if (mDeviceIdList == null) { int numCameras = 0; - ICameraService cameraService = getCameraServiceLocked(); + ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); ArrayList<String> deviceIdList = new ArrayList<>(); // If no camera service, then no devices @@ -515,18 +492,6 @@ public final class CameraManager { return mDeviceIdList; } - private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) { - int problem = e.getReason(); - switch (problem) { - case CameraAccessException.CAMERA_DISCONNECTED: - String errorMsg = CameraAccessException.getDefaultMessage(problem); - Log.w(TAG, msg + ": " + errorMsg); - break; - default: - throw new IllegalStateException(msg, e.asChecked()); - } - } - /** * Queries the camera service if it supports the camera2 api directly, or needs a shim. * @@ -556,7 +521,7 @@ public final class CameraManager { * Anything else is an unexpected error we don't want to recover from. */ try { - ICameraService cameraService = getCameraServiceLocked(); + ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); // If no camera service, no support if (cameraService == null) return false; @@ -578,97 +543,23 @@ public final class CameraManager { } /** - * Connect to the camera service if it's available, and set up listeners. - * - * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p> + * A per-process global camera manager instance, to retain a connection to the camera service, + * and to distribute camera availability notices to API-registered callbacks */ - private void connectCameraServiceLocked() { - mCameraService = null; - IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); - if (cameraServiceBinder == null) { - // Camera service is now down, leave mCameraService as null - return; - } - try { - cameraServiceBinder.linkToDeath(new CameraServiceDeathListener(), /*flags*/ 0); - } catch (RemoteException e) { - // Camera service is now down, leave mCameraService as null - return; - } + private static final class CameraManagerGlobal extends ICameraServiceListener.Stub + implements IBinder.DeathRecipient { + + private static final String TAG = "CameraManagerGlobal"; + private final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); + // Singleton instance + private static final CameraManagerGlobal gCameraManager = + new CameraManagerGlobal(); /** - * Wrap the camera service in a decorator which automatically translates return codes - * into exceptions. + * This must match the ICameraService definition */ - ICameraService cameraService = CameraServiceBinderDecorator.newInstance(cameraServiceRaw); - - try { - CameraServiceBinderDecorator.throwOnError( - CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor()); - } catch (CameraRuntimeException e) { - handleRecoverableSetupErrors(e, "Failed to set up vendor tags"); - } - - try { - cameraService.addListener(mServiceListener); - mCameraService = cameraService; - } catch(CameraRuntimeException e) { - // Unexpected failure - throw new IllegalStateException("Failed to register a camera service listener", - e.asChecked()); - } catch (RemoteException e) { - // Camera service is now down, leave mCameraService as null - } - } - - /** - * Return a best-effort ICameraService. - * - * <p>This will be null if the camera service - * is not currently available. If the camera service has died since the last - * use of the camera service, will try to reconnect to the service.</p> - */ - private ICameraService getCameraServiceLocked() { - if (mCameraService == null) { - Log.i(TAG, "getCameraServiceLocked: Reconnecting to camera service"); - connectCameraServiceLocked(); - if (mCameraService == null) { - Log.e(TAG, "Camera service is unavailable"); - } - } - return mCameraService; - } - - /** - * Listener for camera service death. - * - * <p>The camera service isn't supposed to die under any normal circumstances, but can be turned - * off during debug, or crash due to bugs. So detect that and null out the interface object, so - * that the next calls to the manager can try to reconnect.</p> - */ - private class CameraServiceDeathListener implements IBinder.DeathRecipient { - public void binderDied() { - synchronized(mLock) { - mCameraService = null; - // Tell listeners that the cameras are _available_, because any existing clients - // will have gotten disconnected. This is optimistic under the assumption that the - // service will be back shortly. - // - // Without this, a camera service crash while a camera is open will never signal to - // listeners that previously in-use cameras are now available. - for (String cameraId : mDeviceIdList) { - mServiceListener.onStatusChangedLocked(CameraServiceListener.STATUS_PRESENT, - cameraId); - } - } - } - } - - // TODO: this class needs unit tests - // TODO: extract class into top level - private class CameraServiceListener extends ICameraServiceListener.Stub { + private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; // Keep up-to-date with ICameraServiceListener.h @@ -683,16 +574,112 @@ public final class CameraManager { // Camera is in use by another app and cannot be used exclusively public static final int STATUS_NOT_AVAILABLE = 0x80000000; + // End enums shared with ICameraServiceListener.h + // Camera ID -> Status map private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>(); - private static final String TAG = "CameraServiceListener"; + // Registered availablility callbacks and their handlers + private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap = + new ArrayMap<AvailabilityCallback, Handler>(); + + private final Object mLock = new Object(); + + // Access only through getCameraService to deal with binder death + private ICameraService mCameraService; + + // Singleton, don't allow construction + private CameraManagerGlobal() { + } + + public static CameraManagerGlobal get() { + return gCameraManager; + } @Override public IBinder asBinder() { return this; } + /** + * Return a best-effort ICameraService. + * + * <p>This will be null if the camera service is not currently available. If the camera + * service has died since the last use of the camera service, will try to reconnect to the + * service.</p> + */ + public ICameraService getCameraService() { + synchronized(mLock) { + if (mCameraService == null) { + Log.i(TAG, "getCameraService: Reconnecting to camera service"); + connectCameraServiceLocked(); + if (mCameraService == null) { + Log.e(TAG, "Camera service is unavailable"); + } + } + return mCameraService; + } + } + + /** + * Connect to the camera service if it's available, and set up listeners. + * + * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p> + */ + private void connectCameraServiceLocked() { + mCameraService = null; + IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); + if (cameraServiceBinder == null) { + // Camera service is now down, leave mCameraService as null + return; + } + try { + cameraServiceBinder.linkToDeath(this, /*flags*/ 0); + } catch (RemoteException e) { + // Camera service is now down, leave mCameraService as null + return; + } + + ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); + + /** + * Wrap the camera service in a decorator which automatically translates return codes + * into exceptions. + */ + ICameraService cameraService = + CameraServiceBinderDecorator.newInstance(cameraServiceRaw); + + try { + CameraServiceBinderDecorator.throwOnError( + CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor()); + } catch (CameraRuntimeException e) { + handleRecoverableSetupErrors(e, "Failed to set up vendor tags"); + } + + try { + cameraService.addListener(this); + mCameraService = cameraService; + } catch(CameraRuntimeException e) { + // Unexpected failure + throw new IllegalStateException("Failed to register a camera service listener", + e.asChecked()); + } catch (RemoteException e) { + // Camera service is now down, leave mCameraService as null + } + } + + private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) { + int problem = e.getReason(); + switch (problem) { + case CameraAccessException.CAMERA_DISCONNECTED: + String errorMsg = CameraAccessException.getDefaultMessage(problem); + Log.w(TAG, msg + ": " + errorMsg); + break; + default: + throw new IllegalStateException(msg, e.asChecked()); + } + } + private boolean isAvailable(int status) { switch (status) { case STATUS_PRESENT: @@ -739,7 +726,7 @@ public final class CameraManager { * Send the state of all known cameras to the provided listener, to initialize * the listener's knowledge of camera state. */ - public void updateCallbackLocked(AvailabilityCallback callback, Handler handler) { + private void updateCallbackLocked(AvailabilityCallback callback, Handler handler) { for (int i = 0; i < mDeviceStatus.size(); i++) { String id = mDeviceStatus.keyAt(i); Integer status = mDeviceStatus.valueAt(i); @@ -747,14 +734,7 @@ public final class CameraManager { } } - @Override - public void onStatusChanged(int status, int cameraId) throws RemoteException { - synchronized(CameraManager.this.mLock) { - onStatusChangedLocked(status, String.valueOf(cameraId)); - } - } - - public void onStatusChangedLocked(int status, String id) { + private void onStatusChangedLocked(int status, String id) { if (DEBUG) { Log.v(TAG, String.format("Camera id %s has status changed to 0x%x", id, status)); @@ -811,5 +791,72 @@ public final class CameraManager { } } // onStatusChangedLocked - } // CameraServiceListener + /** + * Register a callback to be notified about camera device availability with the + * global listener singleton. + * + * @param callback the new callback to send camera availability notices to + * @param handler The handler on which the callback should be invoked. May not be null. + */ + public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) { + synchronized (mLock) { + Handler oldHandler = mCallbackMap.put(callback, handler); + // For new callbacks, provide initial availability information + if (oldHandler == null) { + updateCallbackLocked(callback, handler); + } + } + } + + /** + * Remove a previously-added callback; the callback will no longer receive connection and + * disconnection callbacks, and is no longer referenced by the global listener singleton. + * + * @param callback The callback to remove from the notification list + */ + public void unregisterAvailabilityCallback(AvailabilityCallback callback) { + synchronized (mLock) { + mCallbackMap.remove(callback); + } + } + + /** + * Callback from camera service notifying the process about camera availability changes + */ + @Override + public void onStatusChanged(int status, int cameraId) throws RemoteException { + synchronized(mLock) { + onStatusChangedLocked(status, String.valueOf(cameraId)); + } + } + + /** + * Listener for camera service death. + * + * <p>The camera service isn't supposed to die under any normal circumstances, but can be + * turned off during debug, or crash due to bugs. So detect that and null out the interface + * object, so that the next calls to the manager can try to reconnect.</p> + */ + public void binderDied() { + synchronized(mLock) { + // Only do this once per service death + if (mCameraService == null) return; + + mCameraService = null; + + // Tell listeners that the cameras are _available_, because any existing clients + // will have gotten disconnected. This is optimistic under the assumption that + // the service will be back shortly. + // + // Without this, a camera service crash while a camera is open will never signal + // to listeners that previously in-use cameras are now available. + for (int i = 0; i < mDeviceStatus.size(); i++) { + String cameraId = mDeviceStatus.keyAt(i); + onStatusChangedLocked(STATUS_PRESENT, cameraId); + } + } + } + + } // CameraManagerGlobal + } // CameraManager diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 16df844..895ae04 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -450,6 +450,62 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4; + /** + * <p>The camera device supports accurately reporting the sensor settings for many of + * the sensor controls while the built-in 3A algorithm is running. This allows + * reporting of sensor settings even when these settings cannot be manually changed.</p> + * <p>The values reported for the following controls are guaranteed to be available + * in the CaptureResult, including when 3A is enabled:</p> + * <ul> + * <li>Exposure control<ul> + * <li>{@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</li> + * </ul> + * </li> + * <li>Sensitivity control<ul> + * <li>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</li> + * </ul> + * </li> + * <li>Lens controls (if the lens is adjustable)<ul> + * <li>{@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance}</li> + * <li>{@link CaptureRequest#LENS_APERTURE android.lens.aperture}</li> + * </ul> + * </li> + * </ul> + * <p>This capability is a subset of the MANUAL_SENSOR control capability, and will + * always be included if the MANUAL_SENSOR capability is available.</p> + * + * @see CaptureRequest#LENS_APERTURE + * @see CaptureRequest#LENS_FOCUS_DISTANCE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_SENSITIVITY + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; + + /** + * <p>The camera device supports capturing maximum-resolution + * images at >= 20 frames per second, in at least the + * uncompressed YUV format, when post-processing settings + * are set to FAST.</p> + * <p>More specifically, this means that a size matching the + * camera device's active array size is listed as a + * supported size for the YUV_420_888 format in + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}, the minimum frame + * duration for that format and size is <= 1/20 s, and + * the {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges} entry + * lists at least one FPS range where the minimum FPS + * is >= 1 / minimumFrameDuration for the maximum-size + * YUV_420_888 format.</p> + * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is + * guaranted to have a value between 0 and 4, inclusive.</p> + * + * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + * @see CameraCharacteristics#SYNC_MAX_LATENCY + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // @@ -807,9 +863,21 @@ public abstract class CameraMetadata<TKey> { * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are used by the camera * device, along with android.flash.* fields, if there's * a flash unit for this camera device.</p> + * <p>Note that auto-white balance (AWB) and auto-focus (AF) + * behavior is device dependent when AE is in OFF mode. + * To have consistent behavior across different devices, + * it is recommended to either set AWB and AF to OFF mode + * or lock AWB and AF before setting AE to OFF. + * See {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}, + * {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}, and {@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger} + * for more details.</p> * <p>LEGACY devices do not support the OFF mode and will * override attempts to use this value to ON.</p> * + * @see CaptureRequest#CONTROL_AF_MODE + * @see CaptureRequest#CONTROL_AF_TRIGGER + * @see CaptureRequest#CONTROL_AWB_LOCK + * @see CaptureRequest#CONTROL_AWB_MODE * @see CaptureRequest#SENSOR_EXPOSURE_TIME * @see CaptureRequest#SENSOR_FRAME_DURATION * @see CaptureRequest#SENSOR_SENSITIVITY diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 6aec72a..48af67c 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -906,7 +906,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Whether auto-focus (AF) is currently enabled, and what * mode it is set to.</p> * <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus - * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} > 0</code>).</p> + * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} > 0</code>). Also note that + * when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF, the behavior of AF is device + * dependent. It is recommended to lock AF by using {@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger} before + * setting {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} to OFF, or set AF mode to OFF when AE is OFF.</p> * <p>If the lens is controlled by the camera device auto-focus algorithm, * the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState} * in result metadata.</p> @@ -923,8 +926,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@link CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES android.control.afAvailableModes}</p> * <p>This key is available on all devices.</p> * + * @see CaptureRequest#CONTROL_AE_MODE * @see CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES * @see CaptureResult#CONTROL_AF_STATE + * @see CaptureRequest#CONTROL_AF_TRIGGER * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see #CONTROL_AF_MODE_OFF @@ -1046,7 +1051,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>When set to the ON mode, the camera device's auto-white balance * routine is enabled, overriding the application's selected * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p> + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} + * is OFF, the behavior of AWB is device dependent. It is recommened to + * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before + * setting AE mode to OFF.</p> * <p>When set to the OFF mode, the camera device's auto-white balance * routine is disabled. The application manually controls the white * balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} @@ -1077,7 +1085,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CaptureRequest#COLOR_CORRECTION_GAINS * @see CaptureRequest#COLOR_CORRECTION_MODE * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_AE_MODE * @see CameraCharacteristics#CONTROL_AWB_AVAILABLE_MODES + * @see CaptureRequest#CONTROL_AWB_LOCK * @see CaptureRequest#CONTROL_MODE * @see #CONTROL_AWB_MODE_OFF * @see #CONTROL_AWB_MODE_AUTO diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index d208649..c5c843d 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -970,7 +970,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Whether auto-focus (AF) is currently enabled, and what * mode it is set to.</p> * <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus - * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} > 0</code>).</p> + * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} > 0</code>). Also note that + * when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF, the behavior of AF is device + * dependent. It is recommended to lock AF by using {@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger} before + * setting {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} to OFF, or set AF mode to OFF when AE is OFF.</p> * <p>If the lens is controlled by the camera device auto-focus algorithm, * the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState} * in result metadata.</p> @@ -987,8 +990,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@link CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES android.control.afAvailableModes}</p> * <p>This key is available on all devices.</p> * + * @see CaptureRequest#CONTROL_AE_MODE * @see CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES * @see CaptureResult#CONTROL_AF_STATE + * @see CaptureRequest#CONTROL_AF_TRIGGER * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see #CONTROL_AF_MODE_OFF @@ -1519,7 +1524,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When set to the ON mode, the camera device's auto-white balance * routine is enabled, overriding the application's selected * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p> + * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} + * is OFF, the behavior of AWB is device dependent. It is recommened to + * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before + * setting AE mode to OFF.</p> * <p>When set to the OFF mode, the camera device's auto-white balance * routine is disabled. The application manually controls the white * balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} @@ -1550,7 +1558,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureRequest#COLOR_CORRECTION_GAINS * @see CaptureRequest#COLOR_CORRECTION_MODE * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM + * @see CaptureRequest#CONTROL_AE_MODE * @see CameraCharacteristics#CONTROL_AWB_AVAILABLE_MODES + * @see CaptureRequest#CONTROL_AWB_LOCK * @see CaptureRequest#CONTROL_MODE * @see #CONTROL_AWB_MODE_OFF * @see #CONTROL_AWB_MODE_AUTO diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java index 98adcea..3b10eb5 100644 --- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java +++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java @@ -918,7 +918,9 @@ public class ParameterUtils { convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, fakeArea); Point leftEye = face.leftEye, rightEye = face.rightEye, mouth = face.mouth; - if (leftEye != null && rightEye != null && mouth != null) { + if (leftEye != null && rightEye != null && mouth != null && leftEye.x != -2000 && + leftEye.y != -2000 && rightEye.x != -2000 && rightEye.y != -2000 && + mouth.x != -2000 && mouth.y != -2000) { leftEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData, leftEye, /*usePreviewCrop*/true); rightEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData, diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java index e52baed..1f0f45a 100644 --- a/core/java/android/hardware/hdmi/HdmiPortInfo.java +++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java @@ -136,7 +136,7 @@ public final class HdmiPortInfo implements Parcelable { boolean cec = (source.readInt() == 1); boolean arc = (source.readInt() == 1); boolean mhl = (source.readInt() == 1); - return new HdmiPortInfo(id, type, address, cec, arc, mhl); + return new HdmiPortInfo(id, type, address, cec, mhl, arc); } @Override @@ -172,4 +172,15 @@ public final class HdmiPortInfo implements Parcelable { s.append("mhl: ").append(mMhlSupported); return s.toString(); } + + @Override + public boolean equals(Object o) { + if (!(o instanceof HdmiPortInfo)) { + return false; + } + final HdmiPortInfo other = (HdmiPortInfo) o; + return mId == other.mId && mType == other.mType && mAddress == other.mAddress + && mCecSupported == other.mCecSupported && mArcSupported == other.mArcSupported + && mMhlSupported == other.mMhlSupported; + } } diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index af75a0a..3175345 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -31,6 +31,7 @@ import android.inputmethodservice.Keyboard.Key; import android.media.AudioManager; import android.os.Handler; import android.os.Message; +import android.os.UserHandle; import android.provider.Settings; import android.util.AttributeSet; import android.util.TypedValue; @@ -980,8 +981,9 @@ public class KeyboardView extends View implements View.OnClickListener { onInitializeAccessibilityEvent(event); String text = null; // This is very efficient since the properties are cached. - final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; + final boolean speakPassword = Settings.Secure.getIntForUser( + mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0, + UserHandle.USER_CURRENT) != 0; // Add text only if password announcement is enabled or if headset is // used to avoid leaking passwords. if (speakPassword || mAudioManager.isBluetoothA2dpOn() diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 1c9f4c6..d4242ef 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -509,6 +509,8 @@ public class ConnectivityManager { return "MOBILE_EMERGENCY"; case TYPE_PROXY: return "PROXY"; + case TYPE_VPN: + return "VPN"; default: return Integer.toString(type); } @@ -1921,45 +1923,6 @@ public class ConnectivityManager { } /** - * get the information about a specific network link - * @hide - */ - public LinkQualityInfo getLinkQualityInfo(int networkType) { - try { - LinkQualityInfo li = mService.getLinkQualityInfo(networkType); - return li; - } catch (RemoteException e) { - return null; - } - } - - /** - * get the information of currently active network link - * @hide - */ - public LinkQualityInfo getActiveLinkQualityInfo() { - try { - LinkQualityInfo li = mService.getActiveLinkQualityInfo(); - return li; - } catch (RemoteException e) { - return null; - } - } - - /** - * get the information of all network links - * @hide - */ - public LinkQualityInfo[] getAllLinkQualityInfo() { - try { - LinkQualityInfo[] li = mService.getAllLinkQualityInfo(); - return li; - } catch (RemoteException e) { - return null; - } - } - - /** * Set sign in error notification to visible or in visible * * @param visible @@ -2381,15 +2344,14 @@ public class ConnectivityManager { * successfully finding a network for the applications request. Retrieve it with * {@link android.content.Intent#getParcelableExtra(String)}. */ - public static final String EXTRA_NETWORK_REQUEST_NETWORK = "networkRequestNetwork"; + public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; /** * The lookup key for a {@link NetworkRequest} object included with the intent after * successfully finding a network for the applications request. Retrieve it with * {@link android.content.Intent#getParcelableExtra(String)}. */ - public static final String EXTRA_NETWORK_REQUEST_NETWORK_REQUEST = - "networkRequestNetworkRequest"; + public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; /** @@ -2405,8 +2367,8 @@ public class ConnectivityManager { * <receiver> tag in an AndroidManifest.xml file * <p> * The operation Intent is delivered with two extras, a {@link Network} typed - * extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK} and a {@link NetworkRequest} - * typed extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK_REQUEST} containing + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing * the original requests parameters. It is important to create a new, * {@link NetworkCallback} based request before completing the processing of the * Intent to reserve the network or it will be released shortly after the Intent diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java index d965f27..f45737a 100644 --- a/core/java/android/net/EthernetManager.java +++ b/core/java/android/net/EthernetManager.java @@ -18,11 +18,14 @@ package android.net; import android.content.Context; import android.net.IEthernetManager; +import android.net.IEthernetServiceListener; import android.net.IpConfiguration; -import android.net.IpConfiguration.IpAssignment; -import android.net.IpConfiguration.ProxySettings; +import android.os.Handler; +import android.os.Message; import android.os.RemoteException; +import java.util.ArrayList; + /** * A class representing the IP configuration of the Ethernet network. * @@ -30,9 +33,41 @@ import android.os.RemoteException; */ public class EthernetManager { private static final String TAG = "EthernetManager"; + private static final int MSG_AVAILABILITY_CHANGED = 1000; private final Context mContext; private final IEthernetManager mService; + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_AVAILABILITY_CHANGED) { + boolean isAvailable = (msg.arg1 == 1); + for (Listener listener : mListeners) { + listener.onAvailabilityChanged(isAvailable); + } + } + } + }; + private final ArrayList<Listener> mListeners = new ArrayList<Listener>(); + private final IEthernetServiceListener.Stub mServiceListener = + new IEthernetServiceListener.Stub() { + @Override + public void onAvailabilityChanged(boolean isAvailable) { + mHandler.obtainMessage( + MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, null).sendToTarget(); + } + }; + + /** + * A listener interface to receive notification on changes in Ethernet. + */ + public interface Listener { + /** + * Called when Ethernet port's availability is changed. + * @param isAvailable {@code true} if one or more Ethernet port exists. + */ + public void onAvailabilityChanged(boolean isAvailable); + } /** * Create a new EthernetManager instance. @@ -50,12 +85,9 @@ public class EthernetManager { * @return the Ethernet Configuration, contained in {@link IpConfiguration}. */ public IpConfiguration getConfiguration() { - if (mService == null) { - return new IpConfiguration(); - } try { return mService.getConfiguration(); - } catch (RemoteException e) { + } catch (NullPointerException | RemoteException e) { return new IpConfiguration(); } } @@ -64,12 +96,57 @@ public class EthernetManager { * Set Ethernet configuration. */ public void setConfiguration(IpConfiguration config) { - if (mService == null) { - return; - } try { mService.setConfiguration(config); - } catch (RemoteException e) { + } catch (NullPointerException | RemoteException e) { + } + } + + /** + * Indicates whether the system currently has one or more + * Ethernet interfaces. + */ + public boolean isAvailable() { + try { + return mService.isAvailable(); + } catch (NullPointerException | RemoteException e) { + return false; + } + } + + /** + * Adds a listener. + * @param listener A {@link Listener} to add. + * @throws IllegalArgumentException If the listener is null. + */ + public void addListener(Listener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + mListeners.add(listener); + if (mListeners.size() == 1) { + try { + mService.addListener(mServiceListener); + } catch (NullPointerException | RemoteException e) { + } + } + } + + /** + * Removes a listener. + * @param listener A {@link Listener} to remove. + * @throws IllegalArgumentException If the listener is null. + */ + public void removeListener(Listener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + mListeners.remove(listener); + if (mListeners.isEmpty()) { + try { + mService.removeListener(mServiceListener); + } catch (NullPointerException | RemoteException e) { + } } } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a7bbc53..79f920e 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -17,7 +17,6 @@ package android.net; import android.app.PendingIntent; -import android.net.LinkQualityInfo; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -133,12 +132,6 @@ interface IConnectivityManager String getMobileRedirectedProvisioningUrl(); - LinkQualityInfo getLinkQualityInfo(int networkType); - - LinkQualityInfo getActiveLinkQualityInfo(); - - LinkQualityInfo[] getAllLinkQualityInfo(); - void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); void setAirplaneMode(boolean enable); @@ -170,4 +163,5 @@ interface IConnectivityManager boolean addVpnAddress(String address, int prefixLength); boolean removeVpnAddress(String address, int prefixLength); + boolean setUnderlyingNetworksForVpn(in Network[] networks); } diff --git a/core/java/android/net/IEthernetManager.aidl b/core/java/android/net/IEthernetManager.aidl index 3fa08f8..7a92eb9 100644 --- a/core/java/android/net/IEthernetManager.aidl +++ b/core/java/android/net/IEthernetManager.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.IpConfiguration; +import android.net.IEthernetServiceListener; /** * Interface that answers queries about, and allows changing @@ -27,4 +28,7 @@ interface IEthernetManager { IpConfiguration getConfiguration(); void setConfiguration(in IpConfiguration config); + boolean isAvailable(); + void addListener(in IEthernetServiceListener listener); + void removeListener(in IEthernetServiceListener listener); } diff --git a/core/java/android/webkit/MustOverrideException.java b/core/java/android/net/IEthernetServiceListener.aidl index 0643bf0..356690e 100644 --- a/core/java/android/webkit/MustOverrideException.java +++ b/core/java/android/net/IEthernetServiceListener.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,10 @@ * limitations under the License. */ -package android.webkit; +package android.net; -// TODO: Remove MustOverrideException and make all methods throwing it abstract instead; -// needs API file update. -class MustOverrideException extends RuntimeException { - MustOverrideException() { - super("abstract function called: must be overriden!"); - } -}
\ No newline at end of file +/** @hide */ +oneway interface IEthernetServiceListener +{ + void onAvailabilityChanged(boolean isAvailable); +} diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index 36dd2fdfb..d36707e 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -34,7 +34,7 @@ import java.util.Objects; * * @hide */ -public class NetworkIdentity { +public class NetworkIdentity implements Comparable<NetworkIdentity> { /** * When enabled, combine all {@link #mSubType} together under * {@link #SUBTYPE_COMBINED}. @@ -76,7 +76,7 @@ public class NetworkIdentity { @Override public String toString() { - final StringBuilder builder = new StringBuilder("["); + final StringBuilder builder = new StringBuilder("{"); builder.append("type=").append(getNetworkTypeName(mType)); builder.append(", subType="); if (COMBINE_SUBTYPE_ENABLED) { @@ -95,7 +95,7 @@ public class NetworkIdentity { if (mRoaming) { builder.append(", ROAMING"); } - return builder.append("]").toString(); + return builder.append("}").toString(); } public int getType() { @@ -170,4 +170,22 @@ public class NetworkIdentity { return new NetworkIdentity(type, subType, subscriberId, networkId, roaming); } + + @Override + public int compareTo(NetworkIdentity another) { + int res = Integer.compare(mType, another.mType); + if (res == 0) { + res = Integer.compare(mSubType, another.mSubType); + } + if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) { + res = mSubscriberId.compareTo(another.mSubscriberId); + } + if (res == 0 && mNetworkId != null && another.mNetworkId != null) { + res = mNetworkId.compareTo(another.mNetworkId); + } + if (res == 0) { + res = Boolean.compare(mRoaming, another.mRoaming); + } + return res; + } } diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java index 2e0e9e4..d26c70d 100644 --- a/core/java/android/net/NetworkState.java +++ b/core/java/android/net/NetworkState.java @@ -29,20 +29,23 @@ public class NetworkState implements Parcelable { public final NetworkInfo networkInfo; public final LinkProperties linkProperties; public final NetworkCapabilities networkCapabilities; + public final Network network; /** Currently only used by testing. */ public final String subscriberId; public final String networkId; public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties, - NetworkCapabilities networkCapabilities) { - this(networkInfo, linkProperties, networkCapabilities, null, null); + NetworkCapabilities networkCapabilities, Network network) { + this(networkInfo, linkProperties, networkCapabilities, network, null, null); } public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties, - NetworkCapabilities networkCapabilities, String subscriberId, String networkId) { + NetworkCapabilities networkCapabilities, Network network, String subscriberId, + String networkId) { this.networkInfo = networkInfo; this.linkProperties = linkProperties; this.networkCapabilities = networkCapabilities; + this.network = network; this.subscriberId = subscriberId; this.networkId = networkId; } @@ -51,6 +54,7 @@ public class NetworkState implements Parcelable { networkInfo = in.readParcelable(null); linkProperties = in.readParcelable(null); networkCapabilities = in.readParcelable(null); + network = in.readParcelable(null); subscriberId = in.readString(); networkId = in.readString(); } @@ -65,6 +69,7 @@ public class NetworkState implements Parcelable { out.writeParcelable(networkInfo, flags); out.writeParcelable(linkProperties, flags); out.writeParcelable(networkCapabilities, flags); + out.writeParcelable(network, flags); out.writeString(subscriberId); out.writeString(networkId); } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index ea5dfd1..2afe578 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -733,6 +733,22 @@ public class NetworkStats implements Parcelable { } /** + * Return text description of {@link #set} value. + */ + public static String setToCheckinString(int set) { + switch (set) { + case SET_ALL: + return "all"; + case SET_DEFAULT: + return "def"; + case SET_FOREGROUND: + return "fg"; + default: + return "unk"; + } + } + + /** * Return text description of {@link #tag} value. */ public static String tagToString(int tag) { diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 62d8738..4a4accb 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -26,6 +26,7 @@ import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; import static android.net.NetworkStatsHistory.Entry.UNKNOWN; import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.internal.util.ArrayUtils.total; import android.os.Parcel; @@ -38,6 +39,7 @@ import java.io.CharArrayWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.io.PrintWriter; import java.net.ProtocolException; import java.util.Arrays; import java.util.Random; @@ -573,8 +575,22 @@ public class NetworkStatsHistory implements Parcelable { return (long) (start + (r.nextFloat() * (end - start))); } + /** + * Quickly determine if this history intersects with given window. + */ + public boolean intersects(long start, long end) { + final long dataStart = getStart(); + final long dataEnd = getEnd(); + if (start >= dataStart && start <= dataEnd) return true; + if (end >= dataStart && end <= dataEnd) return true; + if (dataStart >= start && dataStart <= end) return true; + if (dataEnd >= start && dataEnd <= end) return true; + return false; + } + public void dump(IndentingPrintWriter pw, boolean fullHistory) { - pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); + pw.print("NetworkStatsHistory: bucketDuration="); + pw.println(bucketDuration / SECOND_IN_MILLIS); pw.increaseIndent(); final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); @@ -583,19 +599,35 @@ public class NetworkStatsHistory implements Parcelable { } for (int i = start; i < bucketCount; i++) { - pw.print("bucketStart="); pw.print(bucketStart[i]); - if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); } - if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); } - if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); } - if (txBytes != null) { pw.print(" txBytes="); pw.print(txBytes[i]); } - if (txPackets != null) { pw.print(" txPackets="); pw.print(txPackets[i]); } - if (operations != null) { pw.print(" operations="); pw.print(operations[i]); } + pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS); + if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); } + if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); } + if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); } + if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); } + if (operations != null) { pw.print(" op="); pw.print(operations[i]); } pw.println(); } pw.decreaseIndent(); } + public void dumpCheckin(PrintWriter pw) { + pw.print("d,"); + pw.print(bucketDuration / SECOND_IN_MILLIS); + pw.println(); + + for (int i = 0; i < bucketCount; i++) { + pw.print("b,"); + pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(','); + if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(','); + if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(','); + if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(','); + if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(','); + if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); } + pw.println(); + } + } + @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index 27197cc..b839e0a 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -16,6 +16,7 @@ package android.net; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI_P2P; @@ -34,10 +35,10 @@ import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; -import java.util.Objects; - import com.android.internal.annotations.VisibleForTesting; +import java.util.Objects; + /** * Template definition used to generically match {@link NetworkIdentity}, * usually when collecting statistics. @@ -53,6 +54,7 @@ public class NetworkTemplate implements Parcelable { public static final int MATCH_ETHERNET = 5; public static final int MATCH_MOBILE_WILDCARD = 6; public static final int MATCH_WIFI_WILDCARD = 7; + public static final int MATCH_BLUETOOTH = 8; /** * Set of {@link NetworkInfo#getType()} that reflect data usage. @@ -134,6 +136,14 @@ public class NetworkTemplate implements Parcelable { return new NetworkTemplate(MATCH_ETHERNET, null, null); } + /** + * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style + * networks together. + */ + public static NetworkTemplate buildTemplateBluetooth() { + return new NetworkTemplate(MATCH_BLUETOOTH, null, null); + } + private final int mMatchRule; private final String mSubscriberId; private final String mNetworkId; @@ -222,6 +232,8 @@ public class NetworkTemplate implements Parcelable { return matchesMobileWildcard(ident); case MATCH_WIFI_WILDCARD: return matchesWifiWildcard(ident); + case MATCH_BLUETOOTH: + return matchesBluetooth(ident); default: throw new IllegalArgumentException("unknown network template"); } @@ -316,6 +328,16 @@ public class NetworkTemplate implements Parcelable { } } + /** + * Check if matches Bluetooth network template. + */ + private boolean matchesBluetooth(NetworkIdentity ident) { + if (ident.mType == TYPE_BLUETOOTH) { + return true; + } + return false; + } + private static String getMatchRuleName(int matchRule) { switch (matchRule) { case MATCH_MOBILE_3G_LOWER: @@ -332,6 +354,8 @@ public class NetworkTemplate implements Parcelable { return "MOBILE_WILDCARD"; case MATCH_WIFI_WILDCARD: return "WIFI_WILDCARD"; + case MATCH_BLUETOOTH: + return "BLUETOOTH"; default: return "UNKNOWN"; } diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index c848993..78b9c18 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -19,6 +19,7 @@ package android.net; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; +import android.annotation.SystemApi; import android.app.Activity; import android.app.PendingIntent; import android.app.Service; @@ -26,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.net.Network; import android.net.NetworkUtils; import android.os.Binder; import android.os.IBinder; @@ -164,6 +166,32 @@ public class VpnService extends Service { } /** + * Version of {@link #prepare(Context)} which does not require user consent. + * + * <p>Requires {@link android.Manifest.permission#CONTROL_VPN} and should generally not be + * used. Only acceptable in situations where user consent has been obtained through other means. + * + * <p>Once this is run, future preparations may be done with the standard prepare method as this + * will authorize the package to prepare the VPN without consent in the future. + * + * @hide + */ + @SystemApi + public static void prepareAndAuthorize(Context context) { + IConnectivityManager cm = getService(); + String packageName = context.getPackageName(); + try { + // Only prepare if we're not already prepared. + if (!cm.prepareVpn(packageName, null)) { + cm.prepareVpn(null, packageName); + } + cm.setVpnPackageAuthorization(true); + } catch (RemoteException e) { + // ignore + } + } + + /** * Protect a socket from VPN connections. After protecting, data sent * through this socket will go directly to the underlying network, * so its traffic will not be forwarded through the VPN. @@ -261,6 +289,46 @@ public class VpnService extends Service { } /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + * Used by the system to know the actual networks that carry traffic for apps affected by this + * VPN in order to present this information to the user (e.g., via status bar icons). + * + * This method only needs to be called if the VPN has explicitly bound its underlying + * communications channels — such as the socket(s) passed to {@link #protect(int)} — + * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or + * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time + * the set of {@code Network}s it is using changes. + * + * {@code networks} is one of the following: + * <ul> + * <li><strong>a non-empty array</strong>: an array of one or more {@link Network}s, in + * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) + * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear + * first in the array.</li> + * <li><strong>an empty array</strong>: a zero-element array, meaning that the VPN has no + * underlying network connection, and thus, app traffic will not be sent or received.</li> + * <li><strong>null</strong>: (default) signifies that the VPN uses whatever is the system's + * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket} + * APIs mentioned above to send traffic over specific channels. + * </ul> + * + * This call will succeed only if the VPN is currently established. For setting this value when + * the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}. + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return {@code true} on success. + */ + public boolean setUnderlyingNetworks(Network[] networks) { + try { + return getService().setUnderlyingNetworksForVpn(networks); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** * Return the communication interface to the service. This method returns * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} * action. Applications overriding this method must identify the intent @@ -636,6 +704,20 @@ public class VpnService extends Service { } /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + * @see VpnService#setUnderlyingNetworks + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + public Builder setUnderlyingNetworks(Network[] networks) { + mConfig.underlyingNetworks = networks != null ? networks.clone() : null; + return this; + } + + /** * Create a VPN interface using the parameters supplied to this * builder. The interface works on IP packets, and a file descriptor * is returned for the application to access them. Each read diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java index 8126b75..24d4eb8 100644 --- a/core/java/android/net/WebAddress.java +++ b/core/java/android/net/WebAddress.java @@ -18,6 +18,8 @@ package android.net; import static android.util.Patterns.GOOD_IRI_CHAR; +import android.annotation.SystemApi; + import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,6 +40,9 @@ import java.util.regex.Pattern; * If given an https scheme but no port, fills in port * */ +// TODO(igsolla): remove WebAddress from the system SDK once the WebView apk does not +// longer need to be binary compatible with the API 21 version of the framework. +@SystemApi public class WebAddress { private String mScheme; @@ -136,42 +141,52 @@ public class WebAddress { return mScheme + "://" + authInfo + mHost + port + mPath; } + /** {@hide} */ public void setScheme(String scheme) { mScheme = scheme; } + /** {@hide} */ public String getScheme() { return mScheme; } + /** {@hide} */ public void setHost(String host) { mHost = host; } + /** {@hide} */ public String getHost() { return mHost; } + /** {@hide} */ public void setPort(int port) { mPort = port; } + /** {@hide} */ public int getPort() { return mPort; } + /** {@hide} */ public void setPath(String path) { mPath = path; } + /** {@hide} */ public String getPath() { return mPath; } + /** {@hide} */ public void setAuthInfo(String authInfo) { mAuthInfo = authInfo; } + /** {@hide} */ public String getAuthInfo() { return mAuthInfo; } diff --git a/core/java/android/net/http/CharArrayBuffers.java b/core/java/android/net/http/CharArrayBuffers.java deleted file mode 100644 index 77d45f6..0000000 --- a/core/java/android/net/http/CharArrayBuffers.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2008 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.net.http; - -import org.apache.http.util.CharArrayBuffer; -import org.apache.http.protocol.HTTP; - -/** - * Utility methods for working on CharArrayBuffers. - * - * {@hide} - */ -class CharArrayBuffers { - - static final char uppercaseAddon = 'a' - 'A'; - - /** - * Returns true if the buffer contains the given string. Ignores leading - * whitespace and case. - * - * @param buffer to search - * @param beginIndex index at which we should start - * @param str to search for - */ - static boolean containsIgnoreCaseTrimmed(CharArrayBuffer buffer, - int beginIndex, final String str) { - int len = buffer.length(); - char[] chars = buffer.buffer(); - while (beginIndex < len && HTTP.isWhitespace(chars[beginIndex])) { - beginIndex++; - } - int size = str.length(); - boolean ok = len >= beginIndex + size; - for (int j=0; ok && (j<size); j++) { - char a = chars[beginIndex+j]; - char b = str.charAt(j); - if (a != b) { - a = toLower(a); - b = toLower(b); - ok = a == b; - } - } - return ok; - } - - /** - * Returns index of first occurence ch. Lower cases characters leading up - * to first occurrence of ch. - */ - static int setLowercaseIndexOf(CharArrayBuffer buffer, final int ch) { - - int beginIndex = 0; - int endIndex = buffer.length(); - char[] chars = buffer.buffer(); - - for (int i = beginIndex; i < endIndex; i++) { - char current = chars[i]; - if (current == ch) { - return i; - } else if (current >= 'A' && current <= 'Z'){ - // make lower case - current += uppercaseAddon; - chars[i] = current; - } - } - return -1; - } - - private static char toLower(char c) { - if (c >= 'A' && c <= 'Z'){ - c += uppercaseAddon; - } - return c; - } -} diff --git a/core/java/android/net/http/Headers.java b/core/java/android/net/http/Headers.java index 657e071..0f8b105 100644 --- a/core/java/android/net/http/Headers.java +++ b/core/java/android/net/http/Headers.java @@ -158,7 +158,7 @@ public final class Headers { } public void parseHeader(CharArrayBuffer buffer) { - int pos = CharArrayBuffers.setLowercaseIndexOf(buffer, ':'); + int pos = setLowercaseIndexOf(buffer, ':'); if (pos == -1) { return; } @@ -459,12 +459,63 @@ public final class Headers { } private void setConnectionType(CharArrayBuffer buffer, int pos) { - if (CharArrayBuffers.containsIgnoreCaseTrimmed( - buffer, pos, HTTP.CONN_CLOSE)) { + if (containsIgnoreCaseTrimmed(buffer, pos, HTTP.CONN_CLOSE)) { connectionType = CONN_CLOSE; - } else if (CharArrayBuffers.containsIgnoreCaseTrimmed( + } else if (containsIgnoreCaseTrimmed( buffer, pos, HTTP.CONN_KEEP_ALIVE)) { connectionType = CONN_KEEP_ALIVE; } } + + + /** + * Returns true if the buffer contains the given string. Ignores leading + * whitespace and case. + * + * @param buffer to search + * @param beginIndex index at which we should start + * @param str to search for + */ + static boolean containsIgnoreCaseTrimmed(CharArrayBuffer buffer, + int beginIndex, final String str) { + int len = buffer.length(); + char[] chars = buffer.buffer(); + while (beginIndex < len && HTTP.isWhitespace(chars[beginIndex])) { + beginIndex++; + } + int size = str.length(); + boolean ok = len >= (beginIndex + size); + for (int j=0; ok && (j < size); j++) { + char a = chars[beginIndex + j]; + char b = str.charAt(j); + if (a != b) { + a = Character.toLowerCase(a); + b = Character.toLowerCase(b); + ok = a == b; + } + } + + return true; + } + + /** + * Returns index of first occurence ch. Lower cases characters leading up + * to first occurrence of ch. + */ + static int setLowercaseIndexOf(CharArrayBuffer buffer, final int ch) { + + int beginIndex = 0; + int endIndex = buffer.length(); + char[] chars = buffer.buffer(); + + for (int i = beginIndex; i < endIndex; i++) { + char current = chars[i]; + if (current == ch) { + return i; + } else { + chars[i] = Character.toLowerCase(current); + } + } + return -1; + } } diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java index 6a63a0c..bb36c20 100644 --- a/core/java/android/net/http/X509TrustManagerExtensions.java +++ b/core/java/android/net/http/X509TrustManagerExtensions.java @@ -36,7 +36,7 @@ import javax.net.ssl.X509TrustManager; */ public class X509TrustManagerExtensions { - TrustManagerImpl mDelegate; + final TrustManagerImpl mDelegate; /** * Constructs a new X509TrustManagerExtensions wrapper. @@ -48,6 +48,7 @@ public class X509TrustManagerExtensions { if (tm instanceof TrustManagerImpl) { mDelegate = (TrustManagerImpl) tm; } else { + mDelegate = null; throw new IllegalArgumentException("tm is an instance of " + tm.getClass().getName() + " which is not a supported type of X509TrustManager"); } diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java index c30ba14..918ec3d 100644 --- a/core/java/android/nfc/BeamShareData.java +++ b/core/java/android/nfc/BeamShareData.java @@ -3,6 +3,7 @@ package android.nfc; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; /** * Class to IPC data to be shared over Android Beam. @@ -14,11 +15,13 @@ import android.os.Parcelable; public final class BeamShareData implements Parcelable { public final NdefMessage ndefMessage; public final Uri[] uris; + public final UserHandle userHandle; public final int flags; - public BeamShareData(NdefMessage msg, Uri[] uris, int flags) { + public BeamShareData(NdefMessage msg, Uri[] uris, UserHandle userHandle, int flags) { this.ndefMessage = msg; this.uris = uris; + this.userHandle = userHandle; this.flags = flags; } @@ -35,6 +38,7 @@ public final class BeamShareData implements Parcelable { if (urisLength > 0) { dest.writeTypedArray(uris, 0); } + dest.writeParcelable(userHandle, 0); dest.writeInt(this.flags); } @@ -49,9 +53,10 @@ public final class BeamShareData implements Parcelable { uris = new Uri[numUris]; source.readTypedArray(uris, Uri.CREATOR); } + UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader()); int flags = source.readInt(); - return new BeamShareData(msg, uris, flags); + return new BeamShareData(msg, uris, userHandle, flags); } @Override diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 5b926ad..961a3f4 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -60,4 +60,6 @@ interface INfcAdapter void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList); void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler); + + void verifyNfcPermission(); } diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index 8643f2e..d009295 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -18,12 +18,14 @@ package android.nfc; import android.app.Activity; import android.app.Application; +import android.content.ContentProvider; import android.content.Intent; import android.net.Uri; import android.nfc.NfcAdapter.ReaderCallback; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -252,7 +254,11 @@ public final class NfcActivityManager extends IAppCallback.Stub isResumed = state.resumed; } if (isResumed) { + // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); + } else { + // Crash API calls early in case NFC permission is missing + verifyNfcPermission(); } } @@ -266,7 +272,11 @@ public final class NfcActivityManager extends IAppCallback.Stub isResumed = state.resumed; } if (isResumed) { + // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); + } else { + // Crash API calls early in case NFC permission is missing + verifyNfcPermission(); } } @@ -279,7 +289,11 @@ public final class NfcActivityManager extends IAppCallback.Stub isResumed = state.resumed; } if (isResumed) { + // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); + } else { + // Crash API calls early in case NFC permission is missing + verifyNfcPermission(); } } @@ -293,7 +307,11 @@ public final class NfcActivityManager extends IAppCallback.Stub isResumed = state.resumed; } if (isResumed) { + // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); + } else { + // Crash API calls early in case NFC permission is missing + verifyNfcPermission(); } } @@ -306,7 +324,11 @@ public final class NfcActivityManager extends IAppCallback.Stub isResumed = state.resumed; } if (isResumed) { + // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); + } else { + // Crash API calls early in case NFC permission is missing + verifyNfcPermission(); } } @@ -322,6 +344,14 @@ public final class NfcActivityManager extends IAppCallback.Stub } } + void verifyNfcPermission() { + try { + NfcAdapter.sService.verifyNfcPermission(); + } catch (RemoteException e) { + mAdapter.attemptDeadServiceRecovery(e); + } + } + /** Callback from NFC service, usually on binder thread */ @Override public BeamShareData createBeamShareData() { @@ -350,19 +380,24 @@ public final class NfcActivityManager extends IAppCallback.Stub if (urisCallback != null) { uris = urisCallback.createBeamUris(mDefaultEvent); if (uris != null) { + ArrayList<Uri> validUris = new ArrayList<Uri>(); for (Uri uri : uris) { if (uri == null) { Log.e(TAG, "Uri not allowed to be null."); - return null; + continue; } String scheme = uri.getScheme(); if (scheme == null || (!scheme.equalsIgnoreCase("file") && !scheme.equalsIgnoreCase("content"))) { Log.e(TAG, "Uri needs to have " + "either scheme file or scheme content"); - return null; + continue; } + uri = ContentProvider.maybeAddUserId(uri, UserHandle.myUserId()); + validUris.add(uri); } + + uris = validUris.toArray(new Uri[validUris.size()]); } } if (uris != null && uris.length > 0) { @@ -372,7 +407,7 @@ public final class NfcActivityManager extends IAppCallback.Stub Intent.FLAG_GRANT_READ_URI_PERMISSION); } } - return new BeamShareData(message, uris, flags); + return new BeamShareData(message, uris, UserHandle.CURRENT, flags); } /** Callback from NFC service, usually on binder thread */ diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 4f91d19..7785f2b 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -209,9 +209,9 @@ public abstract class AsyncTask<Params, Progress, Result> { private static final int MESSAGE_POST_RESULT = 0x1; private static final int MESSAGE_POST_PROGRESS = 0x2; - private static final InternalHandler sHandler = new InternalHandler(); - private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; + private static InternalHandler sHandler; + private final WorkerRunnable<Params, Result> mWorker; private final FutureTask<Result> mFuture; @@ -265,9 +265,13 @@ public abstract class AsyncTask<Params, Progress, Result> { FINISHED, } - /** @hide Used to force static handler to be created. */ - public static void init() { - sHandler.getLooper(); + private static Handler getHandler() { + synchronized (AsyncTask.class) { + if (sHandler == null) { + sHandler = new InternalHandler(); + } + return sHandler; + } } /** @hide */ @@ -315,7 +319,7 @@ public abstract class AsyncTask<Params, Progress, Result> { private Result postResult(Result result) { @SuppressWarnings("unchecked") - Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, + Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; @@ -620,7 +624,7 @@ public abstract class AsyncTask<Params, Progress, Result> { */ protected final void publishProgress(Progress... values) { if (!isCancelled()) { - sHandler.obtainMessage(MESSAGE_POST_PROGRESS, + getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } } @@ -635,10 +639,14 @@ public abstract class AsyncTask<Params, Progress, Result> { } private static class InternalHandler extends Handler { + public InternalHandler() { + super(Looper.getMainLooper()); + } + @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { - AsyncTaskResult result = (AsyncTaskResult) msg.obj; + AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 461469c..4709443 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -3669,6 +3669,45 @@ public abstract class BatteryStats implements Parcelable { pw.print(suffix); } + private static boolean dumpTimeEstimate(PrintWriter pw, String label, long[] steps, + int count, long modesOfInterest, long modeValues) { + if (count <= 0) { + return false; + } + long total = 0; + int numOfInterest = 0; + for (int i=0; i<count; i++) { + long initMode = (steps[i] & STEP_LEVEL_INITIAL_MODE_MASK) + >> STEP_LEVEL_INITIAL_MODE_SHIFT; + long modMode = (steps[i] & STEP_LEVEL_MODIFIED_MODE_MASK) + >> STEP_LEVEL_MODIFIED_MODE_SHIFT; + // If the modes of interest didn't change during this step period... + if ((modMode&modesOfInterest) == 0) { + // And the mode values during this period match those we are measuring... + if ((initMode&modesOfInterest) == modeValues) { + // Then this can be used to estimate the total time! + numOfInterest++; + total += steps[i] & STEP_LEVEL_TIME_MASK; + } + } + } + if (numOfInterest <= 0) { + return false; + } + + // The estimated time is the average time we spend in each level, multipled + // by 100 -- the total number of battery levels + long estimatedTime = (total / numOfInterest) * 100; + + pw.print(label); + StringBuilder sb = new StringBuilder(64); + formatTimeMs(sb, estimatedTime); + pw.print(sb); + pw.println(); + + return true; + } + private static boolean dumpDurationSteps(PrintWriter pw, String header, long[] steps, int count, boolean checkin) { if (count <= 0) { @@ -3923,6 +3962,38 @@ public abstract class BatteryStats implements Parcelable { TimeUtils.formatDuration(timeRemaining / 1000, pw); pw.println(); } + dumpTimeEstimate(pw, " Estimated screen off time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_OFF-1)); + dumpTimeEstimate(pw, " Estimated screen off power save time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_OFF-1)|STEP_LEVEL_MODE_POWER_SAVE); + dumpTimeEstimate(pw, " Estimated screen on time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_ON-1)); + dumpTimeEstimate(pw, " Estimated screen on power save time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_ON-1)|STEP_LEVEL_MODE_POWER_SAVE); + dumpTimeEstimate(pw, " Estimated screen doze time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_DOZE-1)); + dumpTimeEstimate(pw, " Estimated screen doze power save time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_DOZE-1)|STEP_LEVEL_MODE_POWER_SAVE); + dumpTimeEstimate(pw, " Estimated screen doze suspend time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_DOZE_SUSPEND-1)); + dumpTimeEstimate(pw, " Estimated screen doze suspend power save time: ", + getDischargeStepDurationsArray(), getNumDischargeStepDurations(), + STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE, + (Display.STATE_DOZE_SUSPEND-1)|STEP_LEVEL_MODE_POWER_SAVE); pw.println(); } if (dumpDurationSteps(pw, "Charge step durations:", getChargeStepDurationsArray(), diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 72d61e8..b209690 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -20,8 +20,11 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.telephony.TelephonyProperties; + import dalvik.system.VMRuntime; +import java.util.Objects; + /** * Information about the current build, extracted from system properties. */ @@ -641,6 +644,32 @@ public class Build { } } + /** + * Check that device fingerprint is defined and that it matches across + * various partitions. + * + * @hide + */ + public static boolean isFingerprintConsistent() { + final String system = SystemProperties.get("ro.build.fingerprint"); + final String vendor = SystemProperties.get("ro.vendor.build.fingerprint"); + + if (TextUtils.isEmpty(system)) { + Slog.e(TAG, "Required ro.build.fingerprint is empty!"); + return false; + } + + if (!TextUtils.isEmpty(vendor)) { + if (!Objects.equals(system, vendor)) { + Slog.e(TAG, "Mismatched fingerprints; system reported " + system + + " but vendor reported " + vendor); + return false; + } + } + + return true; + } + // The following properties only make sense for internal engineering builds. public static final long TIME = getLong("ro.build.date.utc") * 1000; public static final String USER = getString("ro.build.user"); diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 52db060..878b7a0 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -156,7 +156,7 @@ public class Handler { * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering - * with represent to synchronous messages. Asynchronous messages are not subject to + * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for @@ -176,7 +176,7 @@ public class Handler { * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering - * with represent to synchronous messages. Asynchronous messages are not subject to + * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param callback The callback interface in which to handle messages, or null. @@ -214,7 +214,7 @@ public class Handler { * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering - * with represent to synchronous messages. Asynchronous messages are not subject to + * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param looper The looper, must not be null. diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index 6a0bddc..8c75847 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -417,38 +417,42 @@ public final class Message implements Parcelable { } /** - * Returns true if the message is asynchronous. - * - * Asynchronous messages represent interrupts or events that do not require global ordering - * with represent to synchronous messages. Asynchronous messages are not subject to - * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. + * Returns true if the message is asynchronous, meaning that it is not + * subject to {@link Looper} synchronization barriers. * * @return True if the message is asynchronous. * * @see #setAsynchronous(boolean) - * @see MessageQueue#enqueueSyncBarrier(long) - * @see MessageQueue#removeSyncBarrier(int) - * - * @hide */ public boolean isAsynchronous() { return (flags & FLAG_ASYNCHRONOUS) != 0; } /** - * Sets whether the message is asynchronous. - * - * Asynchronous messages represent interrupts or events that do not require global ordering - * with represent to synchronous messages. Asynchronous messages are not subject to - * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. + * Sets whether the message is asynchronous, meaning that it is not + * subject to {@link Looper} synchronization barriers. + * <p> + * Certain operations, such as view invalidation, may introduce synchronization + * barriers into the {@link Looper}'s message queue to prevent subsequent messages + * from being delivered until some condition is met. In the case of view invalidation, + * messages which are posted after a call to {@link android.view.View#invalidate} + * are suspended by means of a synchronization barrier until the next frame is + * ready to be drawn. The synchronization barrier ensures that the invalidation + * request is completely handled before resuming. + * </p><p> + * Asynchronous messages are exempt from synchronization barriers. They typically + * represent interrupts, input events, and other signals that must be handled independently + * even while other work has been suspended. + * </p><p> + * Note that asynchronous messages may be delivered out of order with respect to + * synchronous messages although they are always delivered in order among themselves. + * If the relative order of these messages matters then they probably should not be + * asynchronous in the first place. Use with caution. + * </p> * * @param async True if the message is asynchronous. * * @see #isAsynchronous() - * @see MessageQueue#enqueueSyncBarrier(long) - * @see MessageQueue#removeSyncBarrier(int) - * - * @hide */ public void setAsynchronous(boolean async) { if (async) { diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 9d78360..6f31768 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -25,6 +25,58 @@ import android.view.Display; */ public abstract class PowerManagerInternal { /** + * Wakefulness: The device is asleep. It can only be awoken by a call to wakeUp(). + * The screen should be off or in the process of being turned off by the display controller. + * The device typically passes through the dozing state first. + */ + public static final int WAKEFULNESS_ASLEEP = 0; + + /** + * Wakefulness: The device is fully awake. It can be put to sleep by a call to goToSleep(). + * When the user activity timeout expires, the device may start dreaming or go to sleep. + */ + public static final int WAKEFULNESS_AWAKE = 1; + + /** + * Wakefulness: The device is dreaming. It can be awoken by a call to wakeUp(), + * which ends the dream. The device goes to sleep when goToSleep() is called, when + * the dream ends or when unplugged. + * User activity may brighten the screen but does not end the dream. + */ + public static final int WAKEFULNESS_DREAMING = 2; + + /** + * Wakefulness: The device is dozing. It is almost asleep but is allowing a special + * low-power "doze" dream to run which keeps the display on but lets the application + * processor be suspended. It can be awoken by a call to wakeUp() which ends the dream. + * The device fully goes to sleep if the dream cannot be started or ends on its own. + */ + public static final int WAKEFULNESS_DOZING = 3; + + public static String wakefulnessToString(int wakefulness) { + switch (wakefulness) { + case WAKEFULNESS_ASLEEP: + return "Asleep"; + case WAKEFULNESS_AWAKE: + return "Awake"; + case WAKEFULNESS_DREAMING: + return "Dreaming"; + case WAKEFULNESS_DOZING: + return "Dozing"; + default: + return Integer.toString(wakefulness); + } + } + + /** + * Returns true if the wakefulness state represents an interactive state + * as defined by {@link android.os.PowerManager#isInteractive}. + */ + public static boolean isInteractive(int wakefulness) { + return wakefulness == WAKEFULNESS_AWAKE || wakefulness == WAKEFULNESS_DREAMING; + } + + /** * Used by the window manager to override the screen brightness based on the * current foreground activity. * diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 0ff5f6a..74e064e 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -45,12 +45,12 @@ public final class UserHandle implements Parcelable { /** @hide A user id to indicate that we would like to send to the current * user, but if this is calling from a user process then we will send it - * to the caller's user instead of failing wiht a security exception */ + * to the caller's user instead of failing with a security exception */ public static final int USER_CURRENT_OR_SELF = -3; /** @hide A user handle to indicate that we would like to send to the current * user, but if this is calling from a user process then we will send it - * to the caller's user instead of failing wiht a security exception */ + * to the caller's user instead of failing with a security exception */ public static final UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF); /** @hide An undefined user id */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index bd6eeea..ffbed94 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -383,8 +383,8 @@ public class UserManager { * * <p/>Key for application restrictions. * <p/>Type: Boolean - * @see android.app.admin.DevicePolicyManager#addApplicationRestriction() - * @see android.app.admin.DevicePolicyManager#getApplicationRestriction() + * @see android.app.admin.DevicePolicyManager#setApplicationRestrictions() + * @see android.app.admin.DevicePolicyManager#getApplicationRestrictions() */ public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending"; diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 3ec45e9..f023df7 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -387,7 +387,6 @@ public class CallLog { public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage) { - // FIXME using -1 as subId instead of SubscriptionManager.INVALID_SUB_ID return addCall(ci, context, number, presentation, callType, features, accountHandle, start, duration, dataUsage, false); } diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 932e873..1316471 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -637,7 +637,7 @@ public abstract class DocumentsProvider extends ContentProvider { final Bundle out = new Bundle(); try { if (METHOD_CREATE_DOCUMENT.equals(method)) { - enforceWritePermissionInner(documentUri); + enforceWritePermissionInner(documentUri, null); final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); @@ -651,7 +651,7 @@ public abstract class DocumentsProvider extends ContentProvider { out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); } else if (METHOD_RENAME_DOCUMENT.equals(method)) { - enforceWritePermissionInner(documentUri); + enforceWritePermissionInner(documentUri, null); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); final String newDocumentId = renameDocument(documentId, displayName); @@ -675,7 +675,7 @@ public abstract class DocumentsProvider extends ContentProvider { } } else if (METHOD_DELETE_DOCUMENT.equals(method)) { - enforceWritePermissionInner(documentUri); + enforceWritePermissionInner(documentUri, null); deleteDocument(documentId); // Document no longer exists, clean up any grants diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8585682..bf91b3e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -984,8 +984,8 @@ public final class Settings { * InputDeviceIdentifier. This field is used by some activities to jump straight into the * settings for the given device. * <p> - * Example: The {@link #INPUT_METHOD_SETTINGS} intent opens the keyboard layout dialog for the - * given device. + * Example: The {@link #ACTION_INPUT_METHOD_SETTINGS} intent opens the keyboard layout + * dialog for the given device. * @hide */ public static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier"; @@ -4816,7 +4816,7 @@ public final class Settings { * The timeout in milliseconds before the device fully goes to sleep after * a period of inactivity. This value sets an upper bound on how long the device * will stay awake or dreaming without user activity. It should generally - * be longer than {@link #SCREEN_OFF_TIMEOUT} as otherwise the device + * be longer than {@link Settings.System#SCREEN_OFF_TIMEOUT} as otherwise the device * will sleep before it ever has a chance to dream. * <p> * Use -1 to disable this timeout. @@ -6601,6 +6601,15 @@ public final class Settings { public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; /** + * Whether user can enable/disable LTE as a preferred network. A carrier might control + * this via gservices, OMA-DM, carrier app, etc. + * <p> + * Type: int (0 for false, 1 for true) + * @hide + */ + public static final String LTE_SERVICE_FORCED = "lte_service_forced"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * diff --git a/core/java/android/service/carriermessaging/CarrierMessagingService.aidl b/core/java/android/service/carriermessaging/CarrierMessagingService.aidl new file mode 100644 index 0000000..50c438a --- /dev/null +++ b/core/java/android/service/carriermessaging/CarrierMessagingService.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.carriermessaging; + +parcelable CarrierMessagingService.SendSmsResponse;
\ No newline at end of file diff --git a/core/java/android/service/carriermessaging/CarrierMessagingService.java b/core/java/android/service/carriermessaging/CarrierMessagingService.java new file mode 100644 index 0000000..7aea590 --- /dev/null +++ b/core/java/android/service/carriermessaging/CarrierMessagingService.java @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.carriermessaging; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.app.Service; +import android.content.Intent; +import android.net.Uri; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +import java.util.List; + +/** + * A service that receives calls from the system when new SMS and MMS are + * sent or received. + * <p>To extend this class, you must declare the service in your manifest file with + * the {@link android.Manifest.permission#BIND_CARRIER_MESSAGING_SERVICE} permission + * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> + * <pre> + * <service android:name=".MyMessagingService" + * android:label="@string/service_name" + * android:permission="android.permission.BIND_CARRIER_MESSAGING_SERVICE"> + * <intent-filter> + * <action android:name="android.service.carriermessaging.CarrierMessagingService" /> + * </intent-filter> + * </service></pre> + */ +public abstract class CarrierMessagingService extends Service { + /** + * The {@link android.content.Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE + = "android.service.carriermessaging.CarrierMessagingService"; + + /** + * Indicates that an SMS or MMS message was successfully sent. + */ + public static final int SEND_STATUS_OK = 0; + + /** + * SMS/MMS sending failed. We should retry via the carrier network. + */ + public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1; + + /** + * SMS/MMS sending failed. We should not retry via the carrier network. + */ + public static final int SEND_STATUS_ERROR = 2; + + /** + * Successfully downloaded an MMS message. + */ + public static final int DOWNLOAD_STATUS_OK = 0; + + /** + * MMS downloading failed. We should retry via the carrier network. + */ + public static final int DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK = 1; + + /** + * MMS downloading failed. We should not retry via the carrier network. + */ + public static final int DOWNLOAD_STATUS_ERROR = 2; + + private final ICarrierMessagingWrapper mWrapper = new ICarrierMessagingWrapper(); + + /** + * Implement this method to filter SMS messages. + * + * @param pdu the PDUs of the message + * @param format the format of the PDUs, typically "3gpp" or "3gpp2" + * @param destPort the destination port of a binary SMS, this will be -1 for text SMS + * + * @return True to keep an inbound SMS message and delivered to SMS apps. False to + * drop the message. + */ + public boolean onFilterSms(@NonNull MessagePdu pdu, @NonNull String format, int destPort) { + // optional + return true; + } + + /** + * Implement this method to intercept text SMSs sent from the devcie. + * + * @param text the text to send + * @param format the format of the response PDU, typically "3gpp" or "3gpp2" + * @param destAddress phone number of the recipient of the message + * + * @return a possibly {code null} {@link SendSmsResponse}. Upon returning {@code null}, the SMS + * is sent using the carrier network. + */ + public @Nullable SendSmsResponse onSendTextSms( + @NonNull String text, @NonNull String format, @NonNull String destAddress) { + // optional + return null; + } + + /** + * Implement this method to intercept binary SMSs sent from the device. + * + * @param data the binary content + * @param format format the format of the response PDU, typically "3gpp" or "3gpp2" + * @param destAddress phone number of the recipient of the message + * @param destPort the destination port + * + * @return a possibly {code null} {@link SendSmsResponse}. Upon returning {@code null}, the SMS + * is sent using the carrier network. + */ + public @Nullable SendSmsResponse onSendDataSms(@NonNull byte[] data, @NonNull String format, + @NonNull String destAddress, int destPort) { + // optional + return null; + } + + /** + * Implement this method to intercept long SMSs sent from the device. + * + * @param parts a {@link List} of the message parts + * @param format format the format of the response PDU, typically "3gpp" or "3gpp2" + * @param destAddress phone number of the recipient of the message + * + * @return a possibly {code null} {@link List} of {@link SendSmsResponse}, one for each message + * part. Upon returning {@code null}, the SMS is sent using the carrier network. + */ + public @Nullable List<SendSmsResponse> onSendMultipartTextSms(@NonNull List<String> parts, + @NonNull String format, @NonNull String destAddress) { + // optional + return null; + } + + /** + * Implement this method to intercept MMSs sent from the device. + * + * @param pduUri the content provider URI of the PDU to send + * @param locationUrl the optional URL to send this MMS PDU. If this is not specified, + * the PDU should be sent to the default MMSC URL. + * + * @return a possibly {@code null} {@link SendMmsResult}. Upon returning {@code null}, the + * MMS is sent using the carrier network. + */ + public @Nullable SendMmsResult onSendMms(@NonNull Uri pduUri, @Nullable String locationUrl) { + // optional + return null; + } + + /** + * Implement this method to download MMSs received. + * + * @param contentUri the content provider URI of the PDU to be downloaded. + * @param locationUrl the URL of the message to be downloaded. + * + * @return a {@link SendMmsResult}. + */ + public int onDownloadMms(@NonNull Uri contentUri, @NonNull String locationUrl) { + // optional + return DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK; + } + + @Override + public @Nullable IBinder onBind(@NonNull Intent intent) { + if (!SERVICE_INTERFACE.equals(intent.getAction())) { + return null; + } + return mWrapper; + } + + /** + * The result of sending an MMS. + */ + public static final class SendMmsResult { + private int mResult; + private byte[] mSendConfPdu; + + /** + * Constructs a SendMmsResult with the MMS send result, and the SenConf PDU. + * + * @param result the result which is one of {@link #SEND_STATUS_OK}, + * {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and + * {@link #SEND_STATUS_ERROR} + * @param sendConfPdu a possibly {code null} SendConf PDU, which confirms that the message + * was sent. sendConfPdu is ignored if the {@code result} is not + * {@link #SEND_STATUS_OK} + */ + public SendMmsResult(int result, @Nullable byte[] sendConfPdu) { + mResult = result; + mSendConfPdu = sendConfPdu; + } + + /** + * Returns the result of sending the MMS. + * + * @return the result which is one of {@link #SEND_STATUS_OK}, + * {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and {@link #SEND_STATUS_ERROR} + */ + public int getResult() { + return mResult; + } + + /** + * Returns the SendConf PDU, which confirms that the message was sent. + * + * @return the SendConf PDU + */ + public @Nullable byte[] getSendConfPdu() { + return mSendConfPdu; + } + } + + /** + * Object passed in callbacks upon successful completion of + * {@link ICarrierMessagingService#sendTextSms}, + * {@link ICarrierMessagingService#sendDataSms}, and + * {@link ICarrierMessagingService#sendMultipartTextSms}. + * Contains message reference and ackPdu. + */ + public static final class SendSmsResponse implements Parcelable { + private int mMessageRef; + private byte[] mAckPdu; + private int mErrorCode; + + /** + * Constructs a SendSmsResponse for the message reference, the ack PDU, and error code for + * the just-sent SMS. + * + * @param messageRef message reference of the just-sent SMS + * @param ackPdu ackPdu for the just-sent SMS + * @param errorCode error code. See 3GPP 27.005, 3.2.5 for GSM/UMTS, + * 3GPP2 N.S0005 (IS-41C) Table 171 for CDMA, -1 if unknown or not applicable. + */ + public SendSmsResponse(int messageRef, @NonNull byte[] ackPdu, int errorCode) { + mMessageRef = messageRef; + mAckPdu = ackPdu; + mErrorCode = errorCode; + } + + /** + * Returns the message reference of the just-sent SMS. + * + * @return the message reference + */ + public int getMessageRef() { + return mMessageRef; + } + + /** + * Returns the ackPdu for the just-sent SMS. + * + * @return the ackPdu + */ + public @NonNull byte[] getAckPdu() { + return mAckPdu; + } + + /** + * Returns the error code upon encountering an error while sending the SMS, -1 if unknown or + * not applicable. + * + * @return errorCode the errorCode as defined in 3GPP 27.005, 3.2.5 for GSM/UMTS, and 3GPP2 + * N.S0005 (IS-41C) Table 171 for CDMA, -1 if unknown or not applicable. + */ + public int getErrorCode() { + return mErrorCode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMessageRef); + dest.writeByteArray(mAckPdu); + dest.writeInt(mErrorCode); + } + + public static final Parcelable.Creator<SendSmsResponse> CREATOR + = new Parcelable.Creator<SendSmsResponse>() { + @Override + public SendSmsResponse createFromParcel(Parcel source) { + return new SendSmsResponse(source.readInt(), + source.createByteArray(), + source.readInt()); + } + + @Override + public SendSmsResponse[] newArray(int size) { + return new SendSmsResponse[size]; + } + }; + } + + /** + * A wrapper around ICarrierMessagingService to enable the carrier messaging APP to implement + * methods it cares about in the {@link ICarrierMessagingService} interface. + */ + private class ICarrierMessagingWrapper extends ICarrierMessagingService.Stub { + @Override + public void filterSms(MessagePdu pdu, String format, int destPort, + ICarrierMessagingCallback callback) { + try { + callback.onFilterComplete(onFilterSms(pdu, format, destPort)); + } catch (RemoteException ex) { + } + } + + @Override + public void sendTextSms(String text, String format, String destAddress, + ICarrierMessagingCallback callback) { + try { + SendSmsResponse sendSmsResponse = onSendTextSms(text, format, destAddress); + if (sendSmsResponse == null) { + callback.onSendSmsComplete(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null); + } else { + callback.onSendSmsComplete(SEND_STATUS_OK, sendSmsResponse); + } + } catch (RemoteException ex) { + } + } + + @Override + public void sendDataSms(byte[] data, String format, String destAddress, int destPort, + ICarrierMessagingCallback callback) { + try { + SendSmsResponse sendSmsResponse = onSendDataSms(data, format, destAddress, + destPort); + if (sendSmsResponse == null) { + callback.onSendSmsComplete(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null); + } else { + callback.onSendSmsComplete(SEND_STATUS_OK, sendSmsResponse); + } + } catch (RemoteException ex) { + } + } + + @Override + public void sendMultipartTextSms(List<String> parts, String format, String destAddress, + ICarrierMessagingCallback callback) { + try { + List<SendSmsResponse> sendSmsResponses = + onSendMultipartTextSms(parts, format, destAddress); + if (sendSmsResponses == null) { + callback.onSendMultipartSmsComplete(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null); + } else { + callback.onSendMultipartSmsComplete(SEND_STATUS_OK, sendSmsResponses); + } + } catch (RemoteException ex) { + } + } + + @Override + public void sendMms(Uri pduUri, String locationUrl, ICarrierMessagingCallback callback) { + try { + SendMmsResult result = onSendMms(pduUri, locationUrl); + if (result == null) { + callback.onSendMmsComplete(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null); + } else { + callback.onSendMmsComplete(SEND_STATUS_OK, result.getSendConfPdu()); + } + } catch (RemoteException ex) { + } + } + + @Override + public void downloadMms(Uri contentUri, String locationUrl, + ICarrierMessagingCallback callback) { + try { + callback.onDownloadMmsComplete(onDownloadMms(contentUri, locationUrl)); + } catch (RemoteException ex) { + } + } + } +} diff --git a/core/java/android/service/carriermessaging/CarrierMessagingServiceManager.java b/core/java/android/service/carriermessaging/CarrierMessagingServiceManager.java new file mode 100644 index 0000000..56ee2c1 --- /dev/null +++ b/core/java/android/service/carriermessaging/CarrierMessagingServiceManager.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.carriermessaging; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; + +import com.android.internal.util.Preconditions; + +/** + * Provides basic structure for platform to connect to the carrier messaging service. + * <p> + * <code> + * CarrierMessagingServiceManager carrierMessagingServiceManager = + * new CarrierMessagingServiceManagerImpl(); + * if (carrierMessagingServiceManager.bindToCarrierMessagingService(context, carrierPackageName)) { + * // wait for onServiceReady callback + * } else { + * // Unable to bind: handle error. + * } + * </code> + * <p> Upon completion {@link #disposeConnection} should be called to unbind the + * CarrierMessagingService. + * @hide + */ +public abstract class CarrierMessagingServiceManager { + // Populated by bindToCarrierMessagingService. bindToCarrierMessagingService must complete + // prior to calling disposeConnection so that mCarrierMessagingServiceConnection is initialized. + private volatile CarrierMessagingServiceConnection mCarrierMessagingServiceConnection; + + /** + * Binds to the carrier messaging service under package {@code carrierPackageName}. This method + * should be called exactly once. + * + * @param context the context + * @param carrierPackageName the carrier package name + * @return true upon successfully binding to a carrier messaging service, false otherwise + */ + public boolean bindToCarrierMessagingService(Context context, String carrierPackageName) { + Preconditions.checkState(mCarrierMessagingServiceConnection == null); + + Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE); + intent.setPackage(carrierPackageName); + mCarrierMessagingServiceConnection = new CarrierMessagingServiceConnection(); + return context.bindService(intent, mCarrierMessagingServiceConnection, + Context.BIND_AUTO_CREATE); + } + + /** + * Unbinds the carrier messaging service. This method should be called exactly once. + * + * @param context the context + */ + public void disposeConnection(Context context) { + Preconditions.checkNotNull(mCarrierMessagingServiceConnection); + context.unbindService(mCarrierMessagingServiceConnection); + mCarrierMessagingServiceConnection = null; + } + + /** + * Implemented by subclasses to use the carrier messaging service once it is ready. + * + * @param carrierMessagingService the carirer messaing service interface + */ + protected abstract void onServiceReady(ICarrierMessagingService carrierMessagingService); + + /** + * A basic {@link ServiceConnection}. + */ + private final class CarrierMessagingServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + onServiceReady(ICarrierMessagingService.Stub.asInterface(service)); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + } +} diff --git a/core/java/android/service/carriermessaging/ICarrierMessagingCallback.aidl b/core/java/android/service/carriermessaging/ICarrierMessagingCallback.aidl new file mode 100644 index 0000000..da56ad1 --- /dev/null +++ b/core/java/android/service/carriermessaging/ICarrierMessagingCallback.aidl @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.carriermessaging; + +import android.service.carriermessaging.CarrierMessagingService; + +/** + * Callback interface definition for the Carrier Messaging Service client to get informed of the + * result of various API invocations. + */ +oneway interface ICarrierMessagingCallback { + void onFilterComplete(boolean keepMessage); + void onSendSmsComplete( + int result, in CarrierMessagingService.SendSmsResponse sendSmsResponse); + void onSendMultipartSmsComplete( + int result, in List<CarrierMessagingService.SendSmsResponse> sendSmsResponses); + void onSendMmsComplete(int result, in byte[] sendConfPdu); + void onDownloadMmsComplete(int result); +} diff --git a/core/java/android/service/carriermessaging/ICarrierMessagingService.aidl b/core/java/android/service/carriermessaging/ICarrierMessagingService.aidl new file mode 100644 index 0000000..6e9e3fa --- /dev/null +++ b/core/java/android/service/carriermessaging/ICarrierMessagingService.aidl @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.carriermessaging; + +import android.net.Uri; +import android.service.carriermessaging.ICarrierMessagingCallback; +import android.service.carriermessaging.MessagePdu; + +/** + * <p class="note"><strong>Note:</strong> + * This service can only be implemented by a carrier privileged app. + */ +oneway interface ICarrierMessagingService { + /** + * Request filtering an incoming SMS message. + * The service will call callback.onFilterComplete with the filtering result. + * + * @param pdu the PDUs of the message + * @param format the format of the PDUs, typically "3gpp" or "3gpp2" + * @param destPort the destination port of a data SMS. It will be -1 for text SMS + * @param callback the callback to notify upon completion + */ + void filterSms( + in MessagePdu pdu, String format, int destPort, in ICarrierMessagingCallback callback); + + /** + * Request sending a new text SMS from the device. + * The service will call {@link ICarrierMessagingCallback#onSendSmsComplete} with the send + * status. + * + * @param text the text to send + * @param format the format of the response PDU, typically "3gpp" or "3gpp2" + * @param destAddress phone number of the recipient of the message + * @param callback the callback to notify upon completion + */ + void sendTextSms(String text, String format, String destAddress, + in ICarrierMessagingCallback callback); + + /** + * Request sending a new data SMS from the device. + * The service will call {@link ICarrierMessagingCallback#onSendSmsComplete} with the send + * status. + * + * @param data the data to send + * @param format the format of the response PDU, typically "3gpp" or "3gpp2" + * @param destAddress phone number of the recipient of the message + * @param destPort port number of the recipient of the message + * @param callback the callback to notify upon completion + */ + void sendDataSms(in byte[] data, String format, String destAddress, int destPort, + in ICarrierMessagingCallback callback); + + /** + * Request sending a new multi-part text SMS from the device. + * The service will call {@link ICarrierMessagingCallback#onSendMultipartSmsComplete} + * with the send status. + * + * @param parts the parts of the multi-part text SMS to send + * @param format the format of the response PDU, typically "3gpp" or "3gpp2" + * @param destAddress phone number of the recipient of the message + * @param callback the callback to notify upon completion + */ + void sendMultipartTextSms(in List<String> parts, String format, String destAddress, + in ICarrierMessagingCallback callback); + + /** + * Request sending a new MMS PDU from the device. + * The service will call {@link ICarrierMessagingCallback#onSendMmsComplete} with the send + * status. + * + * @param pduUri the content provider URI of the PDU to send + * @param locationUrl the optional url to send this MMS PDU. + * If this is not specified, PDU should be sent to the default MMSC url. + * @param callback the callback to notify upon completion + */ + void sendMms(in Uri pduUri, String locationUrl, in ICarrierMessagingCallback callback); + + /** + * Request downloading a new MMS. + * The service will call {@link ICarrierMessagingCallback#onDownloadMmsComplete} with the + * download status. + * + * @param pduUri the content provider URI of the PDU to be downloaded. + * @param locationUrl the URL of the message to be downloaded. + * @param callback the callback to notify upon completion + */ + void downloadMms(in Uri pduUri, String locationUrl, in ICarrierMessagingCallback callback); +} + diff --git a/core/java/android/service/carriermessaging/MessagePdu.aidl b/core/java/android/service/carriermessaging/MessagePdu.aidl new file mode 100644 index 0000000..82b3fb3 --- /dev/null +++ b/core/java/android/service/carriermessaging/MessagePdu.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.carriermessaging; + +parcelable MessagePdu;
\ No newline at end of file diff --git a/core/java/android/service/carriermessaging/MessagePdu.java b/core/java/android/service/carriermessaging/MessagePdu.java new file mode 100644 index 0000000..3c78568 --- /dev/null +++ b/core/java/android/service/carriermessaging/MessagePdu.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.carriermessaging; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A parcelable list of PDUs representing contents of a possibly multi-part SMS. + */ +public final class MessagePdu implements Parcelable { + private static final int NULL_LENGTH = -1; + + private final List<byte[]> mPduList; + + /** + * Constructs a MessagePdu with the list of message PDUs. + * + * @param pduList the list of message PDUs + */ + public MessagePdu(@NonNull List<byte[]> pduList) { + if (pduList == null || pduList.contains(null)) { + throw new IllegalArgumentException("pduList must not be null or contain nulls"); + } + mPduList = pduList; + } + + /** + * Returns the contents of a possibly multi-part SMS. + * + * @return the list of PDUs + */ + public @NonNull List<byte[]> getPdus() { + return mPduList; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mPduList == null) { + dest.writeInt(NULL_LENGTH); + } else { + dest.writeInt(mPduList.size()); + for (byte[] messagePdu : mPduList) { + dest.writeByteArray(messagePdu); + } + } + } + + /** + * Constructs a {@link MessagePdu} from a {@link Parcel}. + */ + public static final Parcelable.Creator<MessagePdu> CREATOR + = new Parcelable.Creator<MessagePdu>() { + @Override + public MessagePdu createFromParcel(Parcel source) { + int size = source.readInt(); + List<byte[]> pduList; + if (size == NULL_LENGTH) { + pduList = null; + } else { + pduList = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + pduList.add(source.createByteArray()); + } + } + return new MessagePdu(pduList); + } + + @Override + public MessagePdu[] newArray(int size) { + return new MessagePdu[size]; + } + }; +} diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 36401eb..ce28d0a 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -474,13 +474,14 @@ public class ZenModeConfig implements Parcelable { return downtime; } - public static Condition toTimeCondition(Context context, int minutesFromNow) { + public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { final long now = System.currentTimeMillis(); final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS; - return toTimeCondition(context, now + millis, minutesFromNow, now); + return toTimeCondition(context, now + millis, minutesFromNow, now, userHandle); } - public static Condition toTimeCondition(Context context, long time, int minutes, long now) { + public static Condition toTimeCondition(Context context, long time, int minutes, long now, + int userHandle) { final int num, summaryResId, line1ResId; if (minutes < 60) { // display as minutes @@ -493,7 +494,7 @@ public class ZenModeConfig implements Parcelable { summaryResId = com.android.internal.R.plurals.zen_mode_duration_hours_summary; line1ResId = com.android.internal.R.plurals.zen_mode_duration_hours; } - final String skeleton = DateFormat.is24HourFormat(context) ? "Hm" : "hma"; + final String skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma"; final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); final CharSequence formattedTime = DateFormat.format(pattern, time); final Resources res = context.getResources(); diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl index bb0c2b2..f07d0d0 100644 --- a/core/java/android/service/trust/ITrustAgentService.aidl +++ b/core/java/android/service/trust/ITrustAgentService.aidl @@ -25,6 +25,8 @@ import android.service.trust.ITrustAgentServiceCallback; interface ITrustAgentService { oneway void onUnlockAttempt(boolean successful); oneway void onTrustTimeout(); + oneway void onDeviceLocked(); + oneway void onDeviceUnlocked(); oneway void onConfigure(in List<PersistableBundle> options, IBinder token); oneway void setCallback(ITrustAgentServiceCallback callback); } diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index d6c997f..62fa978 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -92,6 +92,8 @@ public class TrustAgentService extends Service { private static final int MSG_UNLOCK_ATTEMPT = 1; private static final int MSG_CONFIGURE = 2; private static final int MSG_TRUST_TIMEOUT = 3; + private static final int MSG_DEVICE_LOCKED = 4; + private static final int MSG_DEVICE_UNLOCKED = 5; /** * Class containing raw data for a given configuration request. @@ -134,6 +136,12 @@ public class TrustAgentService extends Service { case MSG_TRUST_TIMEOUT: onTrustTimeout(); break; + case MSG_DEVICE_LOCKED: + onDeviceLocked(); + break; + case MSG_DEVICE_UNLOCKED: + onDeviceUnlocked(); + break; } } }; @@ -173,6 +181,20 @@ public class TrustAgentService extends Service { public void onTrustTimeout() { } + /** + * Called when the device enters a state where a PIN, pattern or + * password must be entered to unlock it. + */ + public void onDeviceLocked() { + } + + /** + * Called when the device leaves a state where a PIN, pattern or + * password must be entered to unlock it. + */ + public void onDeviceUnlocked() { + } + private void onError(String msg) { Slog.v(TAG, "Remote exception while " + msg); } @@ -300,6 +322,16 @@ public class TrustAgentService extends Service { .sendToTarget(); } + @Override + public void onDeviceLocked() throws RemoteException { + mHandler.obtainMessage(MSG_DEVICE_LOCKED).sendToTarget(); + } + + @Override + public void onDeviceUnlocked() throws RemoteException { + mHandler.obtainMessage(MSG_DEVICE_UNLOCKED).sendToTarget(); + } + @Override /* Binder API */ public void setCallback(ITrustAgentServiceCallback callback) { synchronized (mLock) { diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index ceaf5f8..9496b53 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -23,6 +23,7 @@ import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ViewRootImpl; import android.view.WindowInsets; + import com.android.internal.R; import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; @@ -32,18 +33,17 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.app.WallpaperManager; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.PowerManager; import android.os.RemoteException; import android.util.Log; import android.view.Display; @@ -139,7 +139,6 @@ public abstract class WallpaperService extends Service { boolean mInitializing = true; boolean mVisible; - boolean mScreenOn = true; boolean mReportedVisible; boolean mDestroyed; @@ -191,20 +190,10 @@ public abstract class WallpaperService extends Service { float mPendingYOffsetStep; boolean mPendingSync; MotionEvent mPendingMove; - - final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { - mScreenOn = true; - reportVisibility(); - } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - mScreenOn = false; - reportVisibility(); - } - } - }; - + + DisplayManager mDisplayManager; + Display mDisplay; + final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { mRequestedFormat = PixelFormat.RGBX_8888; @@ -536,8 +525,8 @@ public abstract class WallpaperService extends Service { out.print(prefix); out.print("mInitializing="); out.print(mInitializing); out.print(" mDestroyed="); out.println(mDestroyed); out.print(prefix); out.print("mVisible="); out.print(mVisible); - out.print(" mScreenOn="); out.print(mScreenOn); out.print(" mReportedVisible="); out.println(mReportedVisible); + out.print(prefix); out.print("mDisplay="); out.println(mDisplay); out.print(prefix); out.print("mCreated="); out.print(mCreated); out.print(" mSurfaceCreated="); out.print(mSurfaceCreated); out.print(" mIsCreating="); out.print(mIsCreating); @@ -549,7 +538,7 @@ public abstract class WallpaperService extends Service { out.print(prefix); out.print("mType="); out.print(mType); out.print(" mWindowFlags="); out.print(mWindowFlags); out.print(" mCurWindowFlags="); out.println(mCurWindowFlags); - out.print(" mWindowPrivateFlags="); out.print(mWindowPrivateFlags); + out.print(prefix); out.print("mWindowPrivateFlags="); out.print(mWindowPrivateFlags); out.print(" mCurWindowPrivateFlags="); out.println(mCurWindowPrivateFlags); out.print(prefix); out.print("mVisibleInsets="); out.print(mVisibleInsets.toShortString()); @@ -876,13 +865,10 @@ public abstract class WallpaperService extends Service { mWindow.setSession(mSession); - mScreenOn = ((PowerManager)getSystemService(Context.POWER_SERVICE)).isScreenOn(); + mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler()); + mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - registerReceiver(mReceiver, filter); - if (DEBUG) Log.v(TAG, "onCreate(): " + this); onCreate(mSurfaceHolder); @@ -921,7 +907,8 @@ public abstract class WallpaperService extends Service { void reportVisibility() { if (!mDestroyed) { - boolean visible = mVisible && mScreenOn; + boolean visible = mVisible + & mDisplay != null && mDisplay.getState() != Display.STATE_OFF; if (mReportedVisible != visible) { mReportedVisible = visible; if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible @@ -1024,7 +1011,11 @@ public abstract class WallpaperService extends Service { } mDestroyed = true; - + + if (mDisplayManager != null) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + } + if (mVisible) { mVisible = false; if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this); @@ -1035,9 +1026,7 @@ public abstract class WallpaperService extends Service { if (DEBUG) Log.v(TAG, "onDestroy(): " + this); onDestroy(); - - unregisterReceiver(mReceiver); - + if (mCreated) { try { if (DEBUG) Log.v(TAG, "Removing window and destroying surface " @@ -1062,8 +1051,25 @@ public abstract class WallpaperService extends Service { } } } + + private final DisplayListener mDisplayListener = new DisplayListener() { + @Override + public void onDisplayChanged(int displayId) { + if (mDisplay.getDisplayId() == displayId) { + reportVisibility(); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayAdded(int displayId) { + } + }; } - + class IWallpaperEngineWrapper extends IWallpaperEngine.Stub implements HandlerCaller.Callback { private final HandlerCaller mCaller; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 70ad3f0..6cb73ea 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -355,14 +355,12 @@ public class StaticLayout extends Layout { while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { int endPos = paraStart + breaks[breakIndex]; - boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this? - v = out(source, here, endPos, fmAscent, fmDescent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, chs, widths, paraStart, ellipsize, ellipsizedWidth, - lineWidths[breakIndex], paint, moreChars); + lineWidths[breakIndex], paint, true); if (endPos < spanEnd) { // preserve metrics for current span diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index afeb24e..c03f7a6 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -17,6 +17,7 @@ package android.text.format; import android.content.Context; +import android.os.UserHandle; import android.provider.Settings; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -166,8 +167,20 @@ public class DateFormat { * @return true if 24 hour time format is selected, false otherwise. */ public static boolean is24HourFormat(Context context) { - String value = Settings.System.getString(context.getContentResolver(), - Settings.System.TIME_12_24); + return is24HourFormat(context, UserHandle.myUserId()); + } + + /** + * Returns true if user preference with the given user handle is set to 24-hour format. + * @param context the context to use for the content resolver + * @param userHandle the user handle of the user to query. + * @return true if 24 hour time format is selected, false otherwise. + * + * @hide + */ + public static boolean is24HourFormat(Context context, int userHandle) { + String value = Settings.System.getStringForUser(context.getContentResolver(), + Settings.System.TIME_12_24, userHandle); if (value == null) { Locale locale = context.getResources().getConfiguration().locale; @@ -179,7 +192,7 @@ public class DateFormat { } java.text.DateFormat natural = - java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale); + java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale); if (natural instanceof SimpleDateFormat) { SimpleDateFormat sdf = (SimpleDateFormat) natural; @@ -253,74 +266,30 @@ public class DateFormat { * @hide */ public static String getTimeFormatString(Context context) { - LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); - return is24HourFormat(context) ? d.timeFormat_Hm : d.timeFormat_hm; + return getTimeFormatString(context, UserHandle.myUserId()); } /** - * Returns a {@link java.text.DateFormat} object that can format the date - * in short form (such as 12/31/1999) according - * to the current locale and the user's date-order preference. + * Returns a String pattern that can be used to format the time according + * to the current locale and the user's 12-/24-hour clock preference. * @param context the application context - * @return the {@link java.text.DateFormat} object that properly formats the date. + * @param userHandle the user handle of the user to query the format for + * @hide */ - public static java.text.DateFormat getDateFormat(Context context) { - String value = Settings.System.getString(context.getContentResolver(), - Settings.System.DATE_FORMAT); - - return getDateFormatForSetting(context, value); + public static String getTimeFormatString(Context context, int userHandle) { + LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); + return is24HourFormat(context, userHandle) ? d.timeFormat_Hm : d.timeFormat_hm; } /** - * Returns a {@link java.text.DateFormat} object to format the date - * as if the date format setting were set to <code>value</code>, - * including null to use the locale's default format. + * Returns a {@link java.text.DateFormat} object that can format the date + * in short form according to the current locale. + * * @param context the application context - * @param value the date format setting string to interpret for - * the current locale - * @hide + * @return the {@link java.text.DateFormat} object that properly formats the date. */ - public static java.text.DateFormat getDateFormatForSetting(Context context, - String value) { - String format = getDateFormatStringForSetting(context, value); - return new java.text.SimpleDateFormat(format); - } - - private static String getDateFormatStringForSetting(Context context, String value) { - if (value != null) { - int month = value.indexOf('M'); - int day = value.indexOf('d'); - int year = value.indexOf('y'); - - if (month >= 0 && day >= 0 && year >= 0) { - String template = context.getString(R.string.numeric_date_template); - if (year < month && year < day) { - if (month < day) { - value = String.format(template, "yyyy", "MM", "dd"); - } else { - value = String.format(template, "yyyy", "dd", "MM"); - } - } else if (month < day) { - if (day < year) { - value = String.format(template, "MM", "dd", "yyyy"); - } else { // unlikely - value = String.format(template, "MM", "yyyy", "dd"); - } - } else { // day < month - if (month < year) { - value = String.format(template, "dd", "MM", "yyyy"); - } else { // unlikely - value = String.format(template, "dd", "yyyy", "MM"); - } - } - - return value; - } - } - - // The setting is not set; use the locale's default. - LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); - return d.shortDateFormat4; + public static java.text.DateFormat getDateFormat(Context context) { + return java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT); } /** @@ -353,14 +322,16 @@ public class DateFormat { * order returned here. */ public static char[] getDateFormatOrder(Context context) { - return ICU.getDateFormatOrder(getDateFormatString(context)); + return ICU.getDateFormatOrder(getDateFormatString()); } - private static String getDateFormatString(Context context) { - String value = Settings.System.getString(context.getContentResolver(), - Settings.System.DATE_FORMAT); + private static String getDateFormatString() { + java.text.DateFormat df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT); + if (df instanceof SimpleDateFormat) { + return ((SimpleDateFormat) df).toPattern(); + } - return getDateFormatStringForSetting(context, value); + throw new AssertionError("!(df instanceof SimpleDateFormat)"); } /** diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java index a159b40..9749121 100644 --- a/core/java/android/transition/ChangeTransform.java +++ b/core/java/android/transition/ChangeTransform.java @@ -376,7 +376,7 @@ public class ChangeTransform extends Transition { while (outerTransition.mParent != null) { outerTransition = outerTransition.mParent; } - GhostListener listener = new GhostListener(view, ghostView, endMatrix); + GhostListener listener = new GhostListener(view, startValues.view, ghostView); outerTransition.addListener(listener); if (startValues.view != endValues.view) { @@ -466,13 +466,13 @@ public class ChangeTransform extends Transition { private static class GhostListener extends Transition.TransitionListenerAdapter { private View mView; + private View mStartView; private GhostView mGhostView; - private Matrix mEndMatrix; - public GhostListener(View view, GhostView ghostView, Matrix endMatrix) { + public GhostListener(View view, View startView, GhostView ghostView) { mView = view; + mStartView = startView; mGhostView = ghostView; - mEndMatrix = endMatrix; } @Override @@ -481,6 +481,7 @@ public class ChangeTransform extends Transition { GhostView.removeGhost(mView); mView.setTagInternal(R.id.transitionTransform, null); mView.setTagInternal(R.id.parentMatrix, null); + mStartView.setTransitionAlpha(1); } @Override diff --git a/core/java/android/transition/SidePropagation.java b/core/java/android/transition/SidePropagation.java index 623cdd1..ad6c2dd 100644 --- a/core/java/android/transition/SidePropagation.java +++ b/core/java/android/transition/SidePropagation.java @@ -44,8 +44,8 @@ public class SidePropagation extends VisibilityPropagation { * farther from the edge. The default is {@link Gravity#BOTTOM}. * * @param side The side that is used to calculate the transition propagation. Must be one of - * {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT}, or - * {@link Gravity#BOTTOM}. + * {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT}, + * {@link Gravity#BOTTOM}, {@link Gravity#START}, or {@link Gravity#END}. */ public void setSide(int side) { mSide = side; @@ -106,7 +106,7 @@ public class SidePropagation extends VisibilityPropagation { epicenterY = (top + bottom) / 2; } - float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY, + float distance = distance(sceneRoot, viewCenterX, viewCenterY, epicenterX, epicenterY, left, top, right, bottom); float maxDistance = getMaxDistance(sceneRoot); float distanceFraction = distance/maxDistance; @@ -119,10 +119,20 @@ public class SidePropagation extends VisibilityPropagation { return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction); } - private int distance(int viewX, int viewY, int epicenterX, int epicenterY, + private int distance(View sceneRoot, int viewX, int viewY, int epicenterX, int epicenterY, int left, int top, int right, int bottom) { + final int side; + if (mSide == Gravity.START) { + final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + side = isRtl ? Gravity.RIGHT : Gravity.LEFT; + } else if (mSide == Gravity.END) { + final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + side = isRtl ? Gravity.LEFT : Gravity.RIGHT; + } else { + side = mSide; + } int distance = 0; - switch (mSide) { + switch (side) { case Gravity.LEFT: distance = right - viewX + Math.abs(epicenterY - viewY); break; @@ -143,6 +153,8 @@ public class SidePropagation extends VisibilityPropagation { switch (mSide) { case Gravity.LEFT: case Gravity.RIGHT: + case Gravity.START: + case Gravity.END: return sceneRoot.getWidth(); default: return sceneRoot.getHeight(); diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java index ae2e4aa..be1d907 100644 --- a/core/java/android/transition/Slide.java +++ b/core/java/android/transition/Slide.java @@ -76,6 +76,20 @@ public class Slide extends Visibility { } }; + private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() { + @Override + public float getGoneX(ViewGroup sceneRoot, View view) { + final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + final float x; + if (isRtl) { + x = view.getTranslationX() + sceneRoot.getWidth(); + } else { + x = view.getTranslationX() - sceneRoot.getWidth(); + } + return x; + } + }; + private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() { @Override public float getGoneY(ViewGroup sceneRoot, View view) { @@ -90,6 +104,20 @@ public class Slide extends Visibility { } }; + private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() { + @Override + public float getGoneX(ViewGroup sceneRoot, View view) { + final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + final float x; + if (isRtl) { + x = view.getTranslationX() - sceneRoot.getWidth(); + } else { + x = view.getTranslationX() + sceneRoot.getWidth(); + } + return x; + } + }; + private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() { @Override public float getGoneY(ViewGroup sceneRoot, View view) { @@ -144,7 +172,8 @@ public class Slide extends Visibility { * * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, - * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}. + * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, + * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. * @attr ref android.R.styleable#Slide_slideEdge */ public void setSlideEdge(int slideEdge) { @@ -161,6 +190,12 @@ public class Slide extends Visibility { case Gravity.BOTTOM: mSlideCalculator = sCalculateBottom; break; + case Gravity.START: + mSlideCalculator = sCalculateStart; + break; + case Gravity.END: + mSlideCalculator = sCalculateEnd; + break; default: throw new IllegalArgumentException("Invalid slide direction"); } @@ -175,7 +210,8 @@ public class Slide extends Visibility { * * @return the edge of the scene to use for Views appearing and disappearing. One of * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, - * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}. + * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, + * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. * @attr ref android.R.styleable#Slide_slideEdge */ public int getSlideEdge() { diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 36bac31..8779229 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -63,6 +63,7 @@ public abstract class Visibility extends Transition { private static final String[] sTransitionProperties = { PROPNAME_VISIBILITY, + PROPNAME_PARENT, }; private static class VisibilityInfo { diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java index e5f3b2c..92b19be 100644 --- a/core/java/android/util/PathParser.java +++ b/core/java/android/util/PathParser.java @@ -280,7 +280,7 @@ public class PathParser { * @param path The target Path object. */ public static void nodesToPath(PathDataNode[] node, Path path) { - float[] current = new float[4]; + float[] current = new float[6]; char previousCommand = 'm'; for (int i = 0; i < node.length; i++) { addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); @@ -313,6 +313,8 @@ public class PathParser { float currentY = current[1]; float ctrlPointX = current[2]; float ctrlPointY = current[3]; + float currentSegmentStartX = current[4]; + float currentSegmentStartY = current[5]; float reflectiveCtrlPointX; float reflectiveCtrlPointY; @@ -320,7 +322,15 @@ public class PathParser { case 'z': case 'Z': path.close(); - return; + // Path is closed here, but we need to move the pen to the + // closed position. So we cache the segment's starting position, + // and restore it here. + currentX = currentSegmentStartX; + currentY = currentSegmentStartY; + ctrlPointX = currentSegmentStartX; + ctrlPointY = currentSegmentStartY; + path.moveTo(currentX, currentY); + break; case 'm': case 'M': case 'l': @@ -350,17 +360,22 @@ public class PathParser { incr = 7; break; } + for (int k = 0; k < val.length; k += incr) { switch (cmd) { case 'm': // moveto - Start a new sub-path (relative) path.rMoveTo(val[k + 0], val[k + 1]); currentX += val[k + 0]; currentY += val[k + 1]; + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; break; case 'M': // moveto - Start a new sub-path path.moveTo(val[k + 0], val[k + 1]); currentX = val[k + 0]; currentY = val[k + 1]; + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; break; case 'l': // lineto - Draw a line from the current point (relative) path.rLineTo(val[k + 0], val[k + 1]); @@ -372,10 +387,6 @@ public class PathParser { currentX = val[k + 0]; currentY = val[k + 1]; break; - case 'z': // closepath - Close the current subpath - case 'Z': // closepath - Close the current subpath - path.close(); - break; case 'h': // horizontal lineto - Draws a horizontal line (relative) path.rLineTo(val[k + 0], 0); currentX += val[k + 0]; @@ -526,6 +537,8 @@ public class PathParser { current[1] = currentY; current[2] = ctrlPointX; current[3] = ctrlPointY; + current[4] = currentSegmentStartX; + current[5] = currentSegmentStartY; } private static void drawArc(Path p, diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 41a4a7c..19b7861 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -216,7 +216,7 @@ class GLES20Canvas extends HardwareCanvas { /////////////////////////////////////////////////////////////////////////// @Override - public void callDrawGLFunction(long drawGLFunction) { + public void callDrawGLFunction2(long drawGLFunction) { nCallDrawGLFunction(mRenderer, drawGLFunction); } @@ -879,6 +879,9 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { + if ((start | end | (end - start) | (text.length() - end)) < 0) { + throw new IndexOutOfBoundsException(); + } if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags, diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 4e30749..98e3927 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -98,7 +98,7 @@ public abstract class HardwareCanvas extends Canvas { * * @hide */ - public void callDrawGLFunction(long drawGLFunction) { + public void callDrawGLFunction2(long drawGLFunction) { // Noop - this is done in the display list recorder subclass } diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 1ecdf30..e2ad3ad 100644 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -196,6 +196,13 @@ public abstract class InputEvent implements Parcelable { public abstract long getEventTimeNano(); /** + * Marks the input event as being canceled. + * + * @hide + */ + public abstract void cancel(); + + /** * Gets the unique sequence number of this event. * Every input event that is created or received by a process has a * unique sequence number. Moreover, a new sequence number is obtained diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 0701b53..243a0fc 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -2304,6 +2304,16 @@ public class KeyEvent extends InputEvent implements Parcelable { } /** + * Set {@link #FLAG_CANCELED} flag for the key event. + * + * @hide + */ + @Override + public final void cancel() { + mFlags |= FLAG_CANCELED; + } + + /** * Call this during {@link Callback#onKeyDown} to have the system track * the key through its final up (possibly including a long press). Note * that only one key can be tracked at a time -- if another key down diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index ae39b7a..1c5c41c 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3168,6 +3168,12 @@ public final class MotionEvent extends InputEvent implements Parcelable { return ev; } + /** @hide */ + @Override + public final void cancel() { + setAction(ACTION_CANCEL); + } + public void writeToParcel(Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_MOTION_EVENT); nativeWriteToParcel(mNativePtr, out); diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index b95f9a4..7feca30 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -189,9 +189,6 @@ public class RenderNodeAnimator extends Animator { } private void doStart() { - mState = STATE_RUNNING; - nStart(mNativePtr.get(), this); - // Alpha is a special snowflake that has the canonical value stored // in mTransformationInfo instead of in RenderNode, so we need to update // it with the final value here. @@ -201,7 +198,7 @@ public class RenderNodeAnimator extends Animator { mViewTarget.mTransformationInfo.mAlpha = mFinalValue; } - notifyStartListeners(); + moveToRunningState(); if (mViewTarget != null) { // Kick off a frame to start the process @@ -209,6 +206,12 @@ public class RenderNodeAnimator extends Animator { } } + private void moveToRunningState() { + mState = STATE_RUNNING; + nStart(mNativePtr.get(), this); + notifyStartListeners(); + } + private void notifyStartListeners() { final ArrayList<AnimatorListener> listeners = cloneListeners(); final int numListeners = listeners == null ? 0 : listeners.size(); @@ -222,7 +225,7 @@ public class RenderNodeAnimator extends Animator { if (mState != STATE_PREPARE && mState != STATE_FINISHED) { if (mState == STATE_DELAYED) { getHelper().removeDelayedAnimation(this); - notifyStartListeners(); + moveToRunningState(); } nEnd(mNativePtr.get()); @@ -242,7 +245,15 @@ public class RenderNodeAnimator extends Animator { @Override public void end() { if (mState != STATE_FINISHED) { + if (mState < STATE_RUNNING) { + getHelper().removeDelayedAnimation(this); + doStart(); + } nEnd(mNativePtr.get()); + if (mViewTarget != null) { + // Kick off a frame to flush the state change + mViewTarget.invalidateViewProperty(true, false); + } } } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 82ef171..83b8100 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -53,6 +53,9 @@ public class Surface implements Parcelable { private static native void nativeAllocateBuffers(long nativeObject); + private static native int nativeGetWidth(long nativeObject); + private static native int nativeGetHeight(long nativeObject); + public static final Parcelable.Creator<Surface> CREATOR = new Parcelable.Creator<Surface>() { @Override @@ -326,7 +329,9 @@ public class Surface implements Parcelable { if (mHwuiContext == null) { mHwuiContext = new HwuiContext(); } - return mHwuiContext.lockCanvas(); + return mHwuiContext.lockCanvas( + nativeGetWidth(mNativeObject), + nativeGetHeight(mNativeObject)); } } @@ -575,11 +580,11 @@ public class Surface implements Parcelable { mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject); } - Canvas lockCanvas() { + Canvas lockCanvas(int width, int height) { if (mCanvas != null) { throw new IllegalStateException("Surface was already locked!"); } - mCanvas = mRenderNode.start(0, 0); + mCanvas = mRenderNode.start(width, height); return mCanvas; } diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 5579c13..131c039 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -274,7 +274,7 @@ public class ThreadedRenderer extends HardwareRenderer { } private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()"); updateViewTreeDisplayList(view); if (mRootNodeNeedsUpdate || !mRootNode.isValid()) { @@ -340,6 +340,7 @@ public class ThreadedRenderer extends HardwareRenderer { recordDuration, view.getResources().getDisplayMetrics().density); if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) { setEnabled(false); + attachInfo.mViewRootImpl.mSurface.release(); // Invalidate since we failed to draw. This should fetch a Surface // if it is still needed or do nothing if we are no longer drawing attachInfo.mViewRootImpl.invalidate(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a735d54..d74ab13 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -56,6 +56,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; @@ -2400,12 +2401,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80; - /** - * Flag indicating that outline was invalidated and should be rebuilt the next time - * the DisplayList is updated. - */ - static final int PFLAG3_OUTLINE_INVALID = 0x100; - /* End of masks for mPrivateFlags3 */ static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED; @@ -5901,6 +5896,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, region.op(interactiveRegion, Region.Op.INTERSECT); } + // Take into account the window bounds. + final View root = getRootView(); + if (root != null) { + region.op(dx, dy, root.getWidth() + dx, root.getHeight() + dy, Region.Op.INTERSECT); + } + // If the view is completely covered, done. if (region.isEmpty()) { return false; @@ -11271,7 +11272,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setOutlineProvider(ViewOutlineProvider) */ public void invalidateOutline() { - mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID; + rebuildOutline(); notifySubtreeAccessibilityStateChangedIfNeeded(); invalidateViewProperty(false, false); @@ -13174,7 +13175,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } // Should resolve Drawables before Padding because we need the layout direction of the // Drawable to correctly resolve Padding. - if (!isDrawablesResolved()) { + if (!areDrawablesResolved()) { resolveDrawables(); } if (!isPaddingResolved()) { @@ -13438,6 +13439,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void resetResolvedPadding() { + resetResolvedPaddingInternal(); + } + + /** + * Used when we only want to reset *this* view's padding and not trigger overrides + * in ViewGroup that reset children too. + */ + void resetResolvedPaddingInternal() { mPrivateFlags2 &= ~PFLAG2_PADDING_RESOLVED; } @@ -14397,143 +14406,158 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { - mCachingFailed = false; + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "buildDrawingCache/SW Layer for " + getClass().getSimpleName()); + } + try { + buildDrawingCacheImpl(autoScale); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + } - int width = mRight - mLeft; - int height = mBottom - mTop; + /** + * private, internal implementation of buildDrawingCache, used to enable tracing + */ + private void buildDrawingCacheImpl(boolean autoScale) { + mCachingFailed = false; - final AttachInfo attachInfo = mAttachInfo; - final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired; + int width = mRight - mLeft; + int height = mBottom - mTop; + + final AttachInfo attachInfo = mAttachInfo; + final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired; + + if (autoScale && scalingRequired) { + width = (int) ((width * attachInfo.mApplicationScale) + 0.5f); + height = (int) ((height * attachInfo.mApplicationScale) + 0.5f); + } + + final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor; + final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque(); + final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache; - if (autoScale && scalingRequired) { - width = (int) ((width * attachInfo.mApplicationScale) + 0.5f); - height = (int) ((height * attachInfo.mApplicationScale) + 0.5f); + final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4); + final long drawingCacheSize = + ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize(); + if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) { + if (width > 0 && height > 0) { + Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs " + + projectedBitmapSize + " bytes, only " + + drawingCacheSize + " available"); } + destroyDrawingCache(); + mCachingFailed = true; + return; + } - final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor; - final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque(); - final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache; + boolean clear = true; + Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache; - final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4); - final long drawingCacheSize = - ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize(); - if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) { - if (width > 0 && height > 0) { - Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs " - + projectedBitmapSize + " bytes, only " - + drawingCacheSize + " available"); + if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { + Bitmap.Config quality; + if (!opaque) { + // Never pick ARGB_4444 because it looks awful + // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case + switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) { + case DRAWING_CACHE_QUALITY_AUTO: + case DRAWING_CACHE_QUALITY_LOW: + case DRAWING_CACHE_QUALITY_HIGH: + default: + quality = Bitmap.Config.ARGB_8888; + break; } - destroyDrawingCache(); - mCachingFailed = true; - return; + } else { + // Optimization for translucent windows + // If the window is translucent, use a 32 bits bitmap to benefit from memcpy() + quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; } - boolean clear = true; - Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache; + // Try to cleanup memory + if (bitmap != null) bitmap.recycle(); - if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { - Bitmap.Config quality; - if (!opaque) { - // Never pick ARGB_4444 because it looks awful - // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case - switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) { - case DRAWING_CACHE_QUALITY_AUTO: - case DRAWING_CACHE_QUALITY_LOW: - case DRAWING_CACHE_QUALITY_HIGH: - default: - quality = Bitmap.Config.ARGB_8888; - break; - } + try { + bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), + width, height, quality); + bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); + if (autoScale) { + mDrawingCache = bitmap; } else { - // Optimization for translucent windows - // If the window is translucent, use a 32 bits bitmap to benefit from memcpy() - quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; + mUnscaledDrawingCache = bitmap; } - - // Try to cleanup memory - if (bitmap != null) bitmap.recycle(); - - try { - bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), - width, height, quality); - bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); - if (autoScale) { - mDrawingCache = bitmap; - } else { - mUnscaledDrawingCache = bitmap; - } - if (opaque && use32BitCache) bitmap.setHasAlpha(false); - } catch (OutOfMemoryError e) { - // If there is not enough memory to create the bitmap cache, just - // ignore the issue as bitmap caches are not required to draw the - // view hierarchy - if (autoScale) { - mDrawingCache = null; - } else { - mUnscaledDrawingCache = null; - } - mCachingFailed = true; - return; + if (opaque && use32BitCache) bitmap.setHasAlpha(false); + } catch (OutOfMemoryError e) { + // If there is not enough memory to create the bitmap cache, just + // ignore the issue as bitmap caches are not required to draw the + // view hierarchy + if (autoScale) { + mDrawingCache = null; + } else { + mUnscaledDrawingCache = null; } - - clear = drawingCacheBackgroundColor != 0; + mCachingFailed = true; + return; } - Canvas canvas; - if (attachInfo != null) { - canvas = attachInfo.mCanvas; - if (canvas == null) { - canvas = new Canvas(); - } - canvas.setBitmap(bitmap); - // Temporarily clobber the cached Canvas in case one of our children - // is also using a drawing cache. Without this, the children would - // steal the canvas by attaching their own bitmap to it and bad, bad - // thing would happen (invisible views, corrupted drawings, etc.) - attachInfo.mCanvas = null; - } else { - // This case should hopefully never or seldom happen - canvas = new Canvas(bitmap); - } + clear = drawingCacheBackgroundColor != 0; + } - if (clear) { - bitmap.eraseColor(drawingCacheBackgroundColor); + Canvas canvas; + if (attachInfo != null) { + canvas = attachInfo.mCanvas; + if (canvas == null) { + canvas = new Canvas(); } + canvas.setBitmap(bitmap); + // Temporarily clobber the cached Canvas in case one of our children + // is also using a drawing cache. Without this, the children would + // steal the canvas by attaching their own bitmap to it and bad, bad + // thing would happen (invisible views, corrupted drawings, etc.) + attachInfo.mCanvas = null; + } else { + // This case should hopefully never or seldom happen + canvas = new Canvas(bitmap); + } - computeScroll(); - final int restoreCount = canvas.save(); + if (clear) { + bitmap.eraseColor(drawingCacheBackgroundColor); + } - if (autoScale && scalingRequired) { - final float scale = attachInfo.mApplicationScale; - canvas.scale(scale, scale); - } + computeScroll(); + final int restoreCount = canvas.save(); + + if (autoScale && scalingRequired) { + final float scale = attachInfo.mApplicationScale; + canvas.scale(scale, scale); + } - canvas.translate(-mScrollX, -mScrollY); + canvas.translate(-mScrollX, -mScrollY); - mPrivateFlags |= PFLAG_DRAWN; - if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated || - mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID; - } + mPrivateFlags |= PFLAG_DRAWN; + if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated || + mLayerType != LAYER_TYPE_NONE) { + mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID; + } - // Fast path for layouts with no backgrounds - if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { - mPrivateFlags &= ~PFLAG_DIRTY_MASK; - dispatchDraw(canvas); - if (mOverlay != null && !mOverlay.isEmpty()) { - mOverlay.getOverlayView().draw(canvas); - } - } else { - draw(canvas); + // Fast path for layouts with no backgrounds + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { + mPrivateFlags &= ~PFLAG_DIRTY_MASK; + dispatchDraw(canvas); + if (mOverlay != null && !mOverlay.isEmpty()) { + mOverlay.getOverlayView().draw(canvas); } + } else { + draw(canvas); + } - canvas.restoreToCount(restoreCount); - canvas.setBitmap(null); + canvas.restoreToCount(restoreCount); + canvas.setBitmap(null); - if (attachInfo != null) { - // Restore the cached Canvas for our siblings - attachInfo.mCanvas = canvas; - } + if (attachInfo != null) { + // Restore the cached Canvas for our siblings + attachInfo.mCanvas = canvas; } } @@ -14760,27 +14784,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * this view, to which future drawing operations will be clipped. */ public void setClipBounds(Rect clipBounds) { + if (clipBounds == mClipBounds + || (clipBounds != null && clipBounds.equals(mClipBounds))) { + return; + } if (clipBounds != null) { - if (clipBounds.equals(mClipBounds)) { - return; - } if (mClipBounds == null) { - invalidate(); mClipBounds = new Rect(clipBounds); } else { - invalidate(Math.min(mClipBounds.left, clipBounds.left), - Math.min(mClipBounds.top, clipBounds.top), - Math.max(mClipBounds.right, clipBounds.right), - Math.max(mClipBounds.bottom, clipBounds.bottom)); mClipBounds.set(clipBounds); } } else { - if (mClipBounds != null) { - invalidate(); - mClipBounds = null; - } + mClipBounds = null; } mRenderNode.setClipBounds(mClipBounds); + invalidateViewProperty(false, false); } /** @@ -14859,10 +14877,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ void setDisplayListProperties(RenderNode renderNode) { if (renderNode != null) { - if ((mPrivateFlags3 & PFLAG3_OUTLINE_INVALID) != 0) { - rebuildOutline(); - mPrivateFlags3 &= ~PFLAG3_OUTLINE_INVALID; - } renderNode.setHasOverlappingRendering(hasOverlappingRendering()); if (mParent instanceof ViewGroup) { renderNode.setClipToBounds( @@ -15464,7 +15478,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; - mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID; + rebuildOutline(); } // Attempt to use a display list if requested. @@ -15472,10 +15486,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && mAttachInfo.mHardwareRenderer != null) { mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); - final RenderNode displayList = mBackgroundRenderNode; - if (displayList != null && displayList.isValid()) { - setBackgroundDisplayListProperties(displayList); - ((HardwareCanvas) canvas).drawRenderNode(displayList); + final RenderNode renderNode = mBackgroundRenderNode; + if (renderNode != null && renderNode.isValid()) { + setBackgroundRenderNodeProperties(renderNode); + ((HardwareCanvas) canvas).drawRenderNode(renderNode); return; } } @@ -15491,14 +15505,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** - * Set up background drawable display list properties. - * - * @param displayList Valid display list for the background drawable - */ - private void setBackgroundDisplayListProperties(RenderNode displayList) { - displayList.setTranslationX(mScrollX); - displayList.setTranslationY(mScrollY); + private void setBackgroundRenderNodeProperties(RenderNode renderNode) { + renderNode.setTranslationX(mScrollX); + renderNode.setTranslationY(mScrollY); } /** @@ -15847,7 +15856,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mOverlay.getOverlayView().setRight(newWidth); mOverlay.getOverlayView().setBottom(newHeight); } - mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID; + rebuildOutline(); } /** @@ -15883,8 +15892,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(dirty.left + scrollX, dirty.top + scrollY, dirty.right + scrollX, dirty.bottom + scrollY); - - mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID; + rebuildOutline(); } } @@ -15974,6 +15982,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onResolveDrawables(layoutDirection); } + boolean areDrawablesResolved() { + return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED; + } + /** * Called when layout direction has been resolved. * @@ -15993,11 +16005,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ protected void resetResolvedDrawables() { - mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED; + resetResolvedDrawablesInternal(); } - private boolean isDrawablesResolved() { - return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED; + void resetResolvedDrawablesInternal() { + mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED; } /** @@ -16297,10 +16309,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, padding = new Rect(); sThreadLocal.set(padding); } - resetResolvedDrawables(); + resetResolvedDrawablesInternal(); background.setLayoutDirection(getLayoutDirection()); if (background.getPadding(padding)) { - resetResolvedPadding(); + resetResolvedPaddingInternal(); switch (background.getLayoutDirection()) { case LAYOUT_DIRECTION_RTL: mUserPaddingLeftInitial = padding.right; @@ -16500,7 +16512,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param bottom the bottom padding in pixels */ public void setPadding(int left, int top, int right, int bottom) { - resetResolvedPadding(); + resetResolvedPaddingInternal(); mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; @@ -16592,7 +16604,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param bottom the bottom padding in pixels */ public void setPaddingRelative(int start, int top, int end, int bottom) { - resetResolvedPadding(); + resetResolvedPaddingInternal(); mUserPaddingStart = start; mUserPaddingEnd = end; diff --git a/core/java/android/view/ViewAnimationUtils.java b/core/java/android/view/ViewAnimationUtils.java index 001cd01..d44df31 100644 --- a/core/java/android/view/ViewAnimationUtils.java +++ b/core/java/android/view/ViewAnimationUtils.java @@ -17,6 +17,7 @@ package android.view; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; import android.animation.RevealAnimator; /** @@ -35,7 +36,11 @@ public final class ViewAnimationUtils { * {@link View#setClipToOutline(boolean) View Outline clipping}. * <p> * Note that the animation returned here is a one-shot animation. It cannot - * be re-used, and once started it cannot be paused or resumed. + * be re-used, and once started it cannot be paused or resumed. It is also + * an asynchronous animation that automatically runs off of the UI thread. + * As a result {@link AnimatorListener#onAnimationEnd(Animator)} + * will occur after the animation has ended, but it may be delayed depending + * on thread responsiveness. * * @param view The View will be clipped to the animating circle. * @param centerX The x coordinate of the center of the animating circle. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 748e87b..abf23fc 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3534,8 +3534,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * By default, children are clipped to the padding of the ViewGroup. This - * allows view groups to override this behavior + * Sets whether this ViewGroup will clip its children to its padding, if + * padding is present. + * <p> + * By default, children are clipped to the padding of their parent + * Viewgroup. This clipping behavior is only enabled if padding is non-zero. * * @param clipToPadding true to clip children to the padding of the * group, false otherwise @@ -3549,7 +3552,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Check if this ViewGroup is configured to clip child views to its padding. + * Returns whether this ViewGroup will clip its children to its padding, if + * padding is present. + * <p> + * By default, children are clipped to the padding of their parent + * Viewgroup. This clipping behavior is only enabled if padding is non-zero. * * @return true if this ViewGroup clips children to its padding, false otherwise * @@ -6150,7 +6157,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); - if (child.isLayoutDirectionInherited()) { + if (child.isLayoutDirectionInherited() && !child.isPaddingResolved()) { child.resolvePadding(); } } @@ -6165,7 +6172,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); - if (child.isLayoutDirectionInherited()) { + if (child.isLayoutDirectionInherited() && !child.areDrawablesResolved()) { child.resolveDrawables(); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5d2a24b..b12c747 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -245,7 +245,7 @@ public final class ViewRootImpl implements ViewParent, // These can be accessed by any thread, must be protected with a lock. // Surface can never be reassigned or cleared (use Surface.clear()). - private final Surface mSurface = new Surface(); + final Surface mSurface = new Surface(); boolean mAdded; boolean mAddedTouchMode; @@ -3596,12 +3596,19 @@ public final class ViewRootImpl implements ViewParent, if (mView == null || !mAdded) { Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent); return true; - } else if (!mAttachInfo.mHasWindowFocus && - !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && - !isTerminalInputEvent(q.mEvent)) { - // If this is a focused event and the window doesn't currently have input focus, - // then drop this event. This could be an event that came back from the previous - // stage but the window has lost focus in the meantime. + } else if ((!mAttachInfo.mHasWindowFocus || mStopped) + && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { + // This is a focus event and the window doesn't currently have input focus or + // has stopped. This could be an event that came back from the previous stage + // but the window has lost focus or stopped in the meantime. + if (isTerminalInputEvent(q.mEvent)) { + // Don't drop terminal input events, however mark them as canceled. + q.mEvent.cancel(); + Slog.w(TAG, "Cancelling event due to no window focus: " + q.mEvent); + return false; + } + + // Drop non-terminal input events. Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); return true; } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index b85fec8..521fd31 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -37,6 +37,8 @@ public final class ViewTreeObserver { private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners; private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; + private CopyOnWriteArrayList<OnEnterAnimationCompleteListener> + mOnEnterAnimationCompleteListeners; // Non-recursive listeners use CopyOnWriteArray // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive @@ -316,6 +318,13 @@ public final class ViewTreeObserver { } /** + * @hide + */ + public interface OnEnterAnimationCompleteListener { + public void onEnterAnimationComplete(); + } + + /** * Creates a new ViewTreeObserver. This constructor should not be called */ ViewTreeObserver() { @@ -780,6 +789,29 @@ public final class ViewTreeObserver { mOnComputeInternalInsetsListeners.remove(victim); } + /** + * @hide + */ + public void addOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) { + checkIsAlive(); + if (mOnEnterAnimationCompleteListeners == null) { + mOnEnterAnimationCompleteListeners = + new CopyOnWriteArrayList<OnEnterAnimationCompleteListener>(); + } + mOnEnterAnimationCompleteListeners.add(listener); + } + + /** + * @hide + */ + public void removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) { + checkIsAlive(); + if (mOnEnterAnimationCompleteListeners == null) { + return; + } + mOnEnterAnimationCompleteListeners.remove(listener); + } + private void checkIsAlive() { if (!mAlive) { throw new IllegalStateException("This ViewTreeObserver is not alive, call " @@ -1022,6 +1054,23 @@ public final class ViewTreeObserver { } /** + * @hide + */ + public final void dispatchOnEnterAnimationComplete() { + // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to + // perform the dispatching. The iterator is a safe guard against listeners that + // could mutate the list by calling the various add/remove methods. This prevents + // the array from being modified while we iterate it. + final CopyOnWriteArrayList<OnEnterAnimationCompleteListener> listeners = + mOnEnterAnimationCompleteListeners; + if (listeners != null && !listeners.isEmpty()) { + for (OnEnterAnimationCompleteListener listener : listeners) { + listener.onEnterAnimationComplete(); + } + } + } + + /** * Copy on write array. This array is not thread safe, and only one loop can * iterate over this array at any given time. This class avoids allocations * until a concurrent modification happens. diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 0076abf..2e5c1e0 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; @@ -155,6 +156,7 @@ public abstract class Window { "android:navigation:background"; /** The default features enabled */ + @Deprecated @SuppressWarnings({"PointlessBitwiseExpression"}) protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) | (1 << FEATURE_CONTEXT_MENU); @@ -183,8 +185,8 @@ public abstract class Window { private boolean mSetCloseOnTouchOutside = false; private int mForcedWindowFlags = 0; - private int mFeatures = DEFAULT_FEATURES; - private int mLocalFeatures = DEFAULT_FEATURES; + private int mFeatures; + private int mLocalFeatures; private boolean mHaveWindowFormat = false; private boolean mHaveDimAmount = false; @@ -442,6 +444,7 @@ public abstract class Window { public Window(Context context) { mContext = context; + mFeatures = mLocalFeatures = getDefaultFeatures(context); } /** @@ -1270,6 +1273,25 @@ public abstract class Window { } /** + * Return the feature bits set by default on a window. + * @param context The context used to access resources + */ + public static int getDefaultFeatures(Context context) { + int features = 0; + + final Resources res = context.getResources(); + if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureOptionsPanel)) { + features |= 1 << FEATURE_OPTIONS_PANEL; + } + + if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureContextMenu)) { + features |= 1 << FEATURE_CONTEXT_MENU; + } + + return features; + } + + /** * Query for the availability of a certain feature. * * @param feature The feature ID to check diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 82b1073..0d82087 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -118,12 +118,13 @@ public final class WindowManagerGlobal { private Runnable mSystemPropertyUpdater; - /** Default token to apply to added views. */ - private IBinder mDefaultToken; - private WindowManagerGlobal() { } + public static void initialize() { + getWindowManagerService(); + } + public static WindowManagerGlobal getInstance() { synchronized (WindowManagerGlobal.class) { if (sDefaultWindowManager == null) { @@ -138,6 +139,12 @@ public final class WindowManagerGlobal { if (sWindowManagerService == null) { sWindowManagerService = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); + try { + sWindowManagerService = getWindowManagerService(); + ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale()); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get WindowManagerService, cannot set animator scale", e); + } } return sWindowManagerService; } @@ -157,7 +164,6 @@ public final class WindowManagerGlobal { } }, imm.getClient(), imm.getInputContext()); - ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale()); } catch (RemoteException e) { Log.e(TAG, "Failed to open window session", e); } @@ -172,17 +178,6 @@ public final class WindowManagerGlobal { } } - /** - * Sets the default token to use in {@link #addView} when no parent window - * token is available and no token has been explicitly set in the view's - * layout params. - * - * @param token Default window token to apply to added views. - */ - public void setDefaultToken(IBinder token) { - mDefaultToken = token; - } - public String[] getViewRootNames() { synchronized (mLock) { final int numRoots = mRoots.size(); @@ -230,10 +225,6 @@ public final class WindowManagerGlobal { } } - if (wparams.token == null && mDefaultToken != null) { - wparams.token = mDefaultToken; - } - ViewRootImpl root; View panelParentView = null; diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 52d79f8..98e9f54 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -16,6 +16,9 @@ package android.view; +import android.annotation.NonNull; +import android.os.IBinder; + /** * Provides low-level communication with the system window manager for * operations that are bound to a particular context, display or parent window. @@ -47,6 +50,8 @@ public final class WindowManagerImpl implements WindowManager { private final Display mDisplay; private final Window mParentWindow; + private IBinder mDefaultToken; + public WindowManagerImpl(Display display) { this(display, null); } @@ -64,16 +69,43 @@ public final class WindowManagerImpl implements WindowManager { return new WindowManagerImpl(display, mParentWindow); } + /** + * Sets the window token to assign when none is specified by the client or + * available from the parent window. + * + * @param token The default token to assign. + */ + public void setDefaultToken(IBinder token) { + mDefaultToken = token; + } + @Override - public void addView(View view, ViewGroup.LayoutParams params) { + public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { + applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } @Override - public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { + applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } + private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { + // Only use the default token if we don't have a parent window. + if (mDefaultToken != null && mParentWindow == null) { + if (!(params instanceof WindowManager.LayoutParams)) { + throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); + } + + // Only use the default token if we don't already have a token. + final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; + if (wparams.token == null) { + wparams.token = mDefaultToken; + } + } + } + @Override public void removeView(View view) { mGlobal.removeView(view, false); diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 20ef646..eca96f9 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -16,18 +16,14 @@ package android.webkit; +import android.annotation.SystemApi; import android.net.WebAddress; /** * Manages the cookies used by an application's {@link WebView} instances. * Cookies are manipulated according to RFC2109. */ -public class CookieManager { - /** - * @hide Only for use by WebViewProvider implementations - */ - protected CookieManager() { - } +public abstract class CookieManager { @Override protected Object clone() throws CloneNotSupportedException { @@ -59,9 +55,7 @@ public class CookieManager { * @param accept whether {@link WebView} instances should send and accept * cookies */ - public synchronized void setAcceptCookie(boolean accept) { - throw new MustOverrideException(); - } + public abstract void setAcceptCookie(boolean accept); /** * Gets whether the application's {@link WebView} instances send and accept @@ -69,9 +63,7 @@ public class CookieManager { * * @return true if {@link WebView} instances send and accept cookies */ - public synchronized boolean acceptCookie() { - throw new MustOverrideException(); - } + public abstract boolean acceptCookie(); /** * Sets whether the {@link WebView} should allow third party cookies to be set. @@ -87,9 +79,7 @@ public class CookieManager { * @param accept whether the {@link WebView} instance should accept * third party cookies */ - public void setAcceptThirdPartyCookies(WebView webview, boolean accept) { - throw new MustOverrideException(); - } + public abstract void setAcceptThirdPartyCookies(WebView webview, boolean accept); /** * Gets whether the {@link WebView} should allow third party cookies to be set. @@ -97,9 +87,7 @@ public class CookieManager { * @param webview the {@link WebView} instance to get the cookie policy for * @return true if the {@link WebView} accepts third party cookies */ - public boolean acceptThirdPartyCookies(WebView webview) { - throw new MustOverrideException(); - } + public abstract boolean acceptThirdPartyCookies(WebView webview); /** * Sets a cookie for the given URL. Any existing cookie with the same host, @@ -110,9 +98,7 @@ public class CookieManager { * @param value the cookie as a string, using the format of the 'Set-Cookie' * HTTP response header */ - public void setCookie(String url, String value) { - throw new MustOverrideException(); - } + public abstract void setCookie(String url, String value); /** * Sets a cookie for the given URL. Any existing cookie with the same host, @@ -133,9 +119,7 @@ public class CookieManager { * HTTP response header * @param callback a callback to be executed when the cookie has been set */ - public void setCookie(String url, String value, ValueCallback<Boolean> callback) { - throw new MustOverrideException(); - } + public abstract void setCookie(String url, String value, ValueCallback<Boolean> callback); /** * Gets the cookies for the given URL. @@ -144,9 +128,7 @@ public class CookieManager { * @return value the cookies as a string, using the format of the 'Cookie' * HTTP request header */ - public String getCookie(String url) { - throw new MustOverrideException(); - } + public abstract String getCookie(String url); /** * See {@link #getCookie(String)}. @@ -155,11 +137,10 @@ public class CookieManager { * @param privateBrowsing whether to use the private browsing cookie jar * @return value the cookies as a string, using the format of the 'Cookie' * HTTP request header - * @hide Used by Browser, no intention to publish. + * @hide Used by Browser and by WebViewProvider implementations. */ - public String getCookie(String url, boolean privateBrowsing) { - throw new MustOverrideException(); - } + @SystemApi + public abstract String getCookie(String url, boolean privateBrowsing); /** * Gets cookie(s) for a given uri so that it can be set to "cookie:" in http @@ -168,10 +149,11 @@ public class CookieManager { * @param uri the WebAddress for which the cookies are requested * @return value the cookies as a string, using the format of the 'Cookie' * HTTP request header - * @hide Used by RequestHandle, no intention to publish. + * @hide Used by RequestHandle and by WebViewProvider implementations. */ + @SystemApi public synchronized String getCookie(WebAddress uri) { - throw new MustOverrideException(); + return getCookie(uri.toString()); } /** @@ -179,9 +161,7 @@ public class CookieManager { * date. * @deprecated use {@link #removeSessionCookies(ValueCallback)} instead. */ - public void removeSessionCookie() { - throw new MustOverrideException(); - } + public abstract void removeSessionCookie(); /** * Removes all session cookies, which are cookies without an expiration @@ -197,18 +177,14 @@ public class CookieManager { * method from a thread without a Looper. * @param callback a callback which is executed when the session cookies have been removed */ - public void removeSessionCookies(ValueCallback<Boolean> callback) { - throw new MustOverrideException(); - } + public abstract void removeSessionCookies(ValueCallback<Boolean> callback); /** * Removes all cookies. * @deprecated Use {@link #removeAllCookies(ValueCallback)} instead. */ @Deprecated - public void removeAllCookie() { - throw new MustOverrideException(); - } + public abstract void removeAllCookie(); /** * Removes all cookies. @@ -223,54 +199,37 @@ public class CookieManager { * method from a thread without a Looper. * @param callback a callback which is executed when the cookies have been removed */ - public void removeAllCookies(ValueCallback<Boolean> callback) { - throw new MustOverrideException(); - } + public abstract void removeAllCookies(ValueCallback<Boolean> callback); /** * Gets whether there are stored cookies. * * @return true if there are stored cookies */ - public synchronized boolean hasCookies() { - throw new MustOverrideException(); - } + public abstract boolean hasCookies(); /** * See {@link #hasCookies()}. * * @param privateBrowsing whether to use the private browsing cookie jar - * @hide Used by Browser, no intention to publish. + * @hide Used by Browser and WebViewProvider implementations. */ - public synchronized boolean hasCookies(boolean privateBrowsing) { - throw new MustOverrideException(); - } + @SystemApi + public abstract boolean hasCookies(boolean privateBrowsing); /** * Removes all expired cookies. * @deprecated The WebView handles removing expired cookies automatically. */ @Deprecated - public void removeExpiredCookie() { - throw new MustOverrideException(); - } + public abstract void removeExpiredCookie(); /** * Ensures all cookies currently accessible through the getCookie API are * written to persistent storage. * This call will block the caller until it is done and may perform I/O. */ - public void flush() { - flushCookieStore(); - } - - /** - * Flushes all cookies managed by the Chrome HTTP stack to flash. - * - * @hide Package level api, called from CookieSyncManager - */ - protected void flushCookieStore() { - } + public abstract void flush(); /** * Gets whether the application's {@link WebView} instances send and accept @@ -289,9 +248,8 @@ public class CookieManager { * * @hide Only for use by WebViewProvider implementations */ - protected boolean allowFileSchemeCookiesImpl() { - throw new MustOverrideException(); - } + @SystemApi + protected abstract boolean allowFileSchemeCookiesImpl(); /** * Sets whether the application's {@link WebView} instances should send and @@ -314,7 +272,6 @@ public class CookieManager { * * @hide Only for use by WebViewProvider implementations */ - protected void setAcceptFileSchemeCookiesImpl(boolean accept) { - throw new MustOverrideException(); - } + @SystemApi + protected abstract void setAcceptFileSchemeCookiesImpl(boolean accept); } diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java deleted file mode 100644 index 7b3cb1b..0000000 --- a/core/java/android/webkit/DebugFlags.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2009 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.webkit; - -/** - * This class is a container for all of the debug flags used in the Java - * components of webkit. These flags must be final in order to ensure that - * the compiler optimizes the code that uses them out of the final executable. - * - * The name of each flags maps directly to the name of the class in which that - * flag is used. - * - * @hide Only used by WebView implementations. - */ -public class DebugFlags { - - public static final boolean COOKIE_SYNC_MANAGER = false; - public static final boolean TRACE_API = false; - public static final boolean TRACE_CALLBACK = false; - public static final boolean URL_UTIL = false; - public static final boolean WEB_SYNC_MANAGER = false; - -} diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java index c68b450..ab6a2f9 100644 --- a/core/java/android/webkit/FindActionModeCallback.java +++ b/core/java/android/webkit/FindActionModeCallback.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; @@ -36,6 +37,7 @@ import android.widget.TextView; /** * @hide */ +@SystemApi public class FindActionModeCallback implements ActionMode.Callback, TextWatcher, View.OnClickListener, WebView.FindListener { private View mCustomView; diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java index bc3d035..7187f22 100644 --- a/core/java/android/webkit/GeolocationPermissions.java +++ b/core/java/android/webkit/GeolocationPermissions.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.SystemApi; + import java.util.Set; /** @@ -136,5 +138,6 @@ public class GeolocationPermissions { * way to call createHandler() and createUIHandler(), so it would not work). * @hide Only for use by WebViewProvider implementations */ + @SystemApi public GeolocationPermissions() {} } diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java index ee3b369..45fc1f5 100644 --- a/core/java/android/webkit/HttpAuthHandler.java +++ b/core/java/android/webkit/HttpAuthHandler.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.os.Handler; /** @@ -30,6 +31,7 @@ public class HttpAuthHandler extends Handler { /** * @hide Only for use by WebViewProvider implementations. */ + @SystemApi public HttpAuthHandler() { } diff --git a/core/java/android/webkit/JsDialogHelper.java b/core/java/android/webkit/JsDialogHelper.java index bb0339e..cc475c3 100644 --- a/core/java/android/webkit/JsDialogHelper.java +++ b/core/java/android/webkit/JsDialogHelper.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; @@ -34,8 +35,9 @@ import java.net.URL; * Helper class to create JavaScript dialogs. It is used by * different WebView implementations. * - * @hide Helper class for internal use + * @hide */ +@SystemApi public class JsDialogHelper { private static final String TAG = "JsDialogHelper"; diff --git a/core/java/android/webkit/JsPromptResult.java b/core/java/android/webkit/JsPromptResult.java index a1bf124..771cc32 100644 --- a/core/java/android/webkit/JsPromptResult.java +++ b/core/java/android/webkit/JsPromptResult.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.SystemApi; + /** * Public class for handling JavaScript prompt requests. The WebChromeClient will receive a @@ -39,6 +41,7 @@ public class JsPromptResult extends JsResult { /** * @hide Only for use by WebViewProvider implementations */ + @SystemApi public JsPromptResult(ResultReceiver receiver) { super(receiver); } @@ -46,6 +49,7 @@ public class JsPromptResult extends JsResult { /** * @hide Only for use by WebViewProvider implementations */ + @SystemApi public String getStringResult() { return mStringResult; } diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java index e4e6851..d36ab41 100644 --- a/core/java/android/webkit/JsResult.java +++ b/core/java/android/webkit/JsResult.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.SystemApi; + /** * An instance of this class is passed as a parameter in various {@link WebChromeClient} action * notifications. The object is used as a handle onto the underlying JavaScript-originated request, @@ -27,6 +29,7 @@ public class JsResult { * notifications when the JavaScript result represented by a JsResult instance has * @hide Only for use by WebViewProvider implementations */ + @SystemApi public interface ResultReceiver { public void onJsResultComplete(JsResult result); } @@ -54,6 +57,7 @@ public class JsResult { /** * @hide Only for use by WebViewProvider implementations */ + @SystemApi public JsResult(ResultReceiver receiver) { mReceiver = receiver; } @@ -61,6 +65,7 @@ public class JsResult { /** * @hide Only for use by WebViewProvider implementations */ + @SystemApi public final boolean getResult() { return mResult; } diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java index af31544..537065d 100644 --- a/core/java/android/webkit/SslErrorHandler.java +++ b/core/java/android/webkit/SslErrorHandler.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.os.Handler; /** @@ -30,6 +31,7 @@ public class SslErrorHandler extends Handler { /** * @hide Only for use by WebViewProvider implementations. */ + @SystemApi public SslErrorHandler() {} /** diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index d115984..f5233b6 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -29,6 +29,7 @@ import android.util.Log; public final class URLUtil { private static final String LOGTAG = "webkit"; + private static final boolean TRACE = false; // to refer to bar.png under your package's asset/foo/ directory, use // "file:///android_asset/foo/bar.png". @@ -49,7 +50,7 @@ public final class URLUtil { String retVal = inUrl; WebAddress webAddress; - if (DebugFlags.URL_UTIL) Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl); + if (TRACE) Log.v(LOGTAG, "guessURL before queueRequest: " + inUrl); if (inUrl.length() == 0) return inUrl; if (inUrl.startsWith("about:")) return inUrl; @@ -69,7 +70,7 @@ public final class URLUtil { webAddress = new WebAddress(inUrl); } catch (ParseException ex) { - if (DebugFlags.URL_UTIL) { + if (TRACE) { Log.v(LOGTAG, "smartUrlFilter: failed to parse url = " + inUrl); } return retVal; @@ -286,7 +287,7 @@ public final class URLUtil { } return url; } - + /** * Guesses canonical filename that a download would have, using * the URL and contentDisposition. File extension, if not defined, @@ -294,7 +295,7 @@ public final class URLUtil { * @param url Url to the content * @param contentDisposition Content-Disposition HTTP header or null * @param mimeType Mime-type of the content or null - * + * * @return suggested filename */ public static final String guessFileName( diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java index bfef2e7..e671376 100644 --- a/core/java/android/webkit/WebBackForwardList.java +++ b/core/java/android/webkit/WebBackForwardList.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import java.io.Serializable; /** @@ -23,56 +24,38 @@ import java.io.Serializable; * WebView.copyBackForwardList() will return a copy of this class used to * inspect the entries in the list. */ -public class WebBackForwardList implements Cloneable, Serializable { - - /** - * @hide - */ - public WebBackForwardList() { - } - +public abstract class WebBackForwardList implements Cloneable, Serializable { /** * Return the current history item. This method returns null if the list is * empty. * @return The current history item. */ - public synchronized WebHistoryItem getCurrentItem() { - throw new MustOverrideException(); - } + public abstract WebHistoryItem getCurrentItem(); /** * Get the index of the current history item. This index can be used to * directly index into the array list. * @return The current index from 0...n or -1 if the list is empty. */ - public synchronized int getCurrentIndex() { - throw new MustOverrideException(); - } + public abstract int getCurrentIndex(); /** * Get the history item at the given index. The index range is from 0...n * where 0 is the first item and n is the last item. * @param index The index to retrieve. */ - public synchronized WebHistoryItem getItemAtIndex(int index) { - throw new MustOverrideException(); - } + public abstract WebHistoryItem getItemAtIndex(int index); /** * Get the total size of the back/forward list. * @return The size of the list. */ - public synchronized int getSize() { - throw new MustOverrideException(); - } + public abstract int getSize(); /** * Clone the entire object to be used in the UI thread by clients of * WebView. This creates a copy that should never be modified by any of the * webkit package classes. */ - protected synchronized WebBackForwardList clone() { - throw new MustOverrideException(); - } - + protected abstract WebBackForwardList clone(); } diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 46a7fd0..768dc9f 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; @@ -509,6 +510,7 @@ public class WebChromeClient { * @deprecated Use {@link #showFileChooser} instead. * @hide This method was not published in any SDK version. */ + @SystemApi @Deprecated public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) { uploadFile.onReceiveValue(null); diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java index 9a588e4..569fccd 100644 --- a/core/java/android/webkit/WebHistoryItem.java +++ b/core/java/android/webkit/WebHistoryItem.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.graphics.Bitmap; /** @@ -24,14 +25,7 @@ import android.graphics.Bitmap; * item. Each history item may be updated during the load of a page. * @see WebBackForwardList */ -public class WebHistoryItem implements Cloneable { - - /** - * @hide - */ - public WebHistoryItem() { - } - +public abstract class WebHistoryItem implements Cloneable { /** * Return an identifier for this history item. If an item is a copy of * another item, the identifiers will be the same even if they are not the @@ -40,10 +34,9 @@ public class WebHistoryItem implements Cloneable { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SystemApi @Deprecated - public int getId() { - throw new MustOverrideException(); - } + public abstract int getId(); /** * Return the url of this history item. The url is the base url of this @@ -53,29 +46,23 @@ public class WebHistoryItem implements Cloneable { * Note: The VM ensures 32-bit atomic read/write operations so we don't have * to synchronize this method. */ - public String getUrl() { - throw new MustOverrideException(); - } + public abstract String getUrl(); /** * Return the original url of this history item. This was the requested - * url, the final url may be different as there might have been + * url, the final url may be different as there might have been * redirects while loading the site. * @return The original url of this history item. */ - public String getOriginalUrl() { - throw new MustOverrideException(); - } - + public abstract String getOriginalUrl(); + /** * Return the document title of this history item. * @return The document title of this history item. * Note: The VM ensures 32-bit atomic read/write operations so we don't have * to synchronize this method. */ - public String getTitle() { - throw new MustOverrideException(); - } + public abstract String getTitle(); /** * Return the favicon of this history item or null if no favicon was found. @@ -83,15 +70,10 @@ public class WebHistoryItem implements Cloneable { * Note: The VM ensures 32-bit atomic read/write operations so we don't have * to synchronize this method. */ - public Bitmap getFavicon() { - throw new MustOverrideException(); - } + public abstract Bitmap getFavicon(); /** * Clone the history item for use by clients of WebView. */ - protected synchronized WebHistoryItem clone() { - throw new MustOverrideException(); - } - + protected abstract WebHistoryItem clone(); } diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java index e574593..08956e0 100644 --- a/core/java/android/webkit/WebIconDatabase.java +++ b/core/java/android/webkit/WebIconDatabase.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.content.ContentResolver; import android.graphics.Bitmap; @@ -32,7 +33,7 @@ import android.graphics.Bitmap; * up to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} */ @Deprecated -public class WebIconDatabase { +public abstract class WebIconDatabase { /** * Interface for receiving icons from the database. * @deprecated This interface is obsolete. @@ -52,23 +53,17 @@ public class WebIconDatabase { * Open a the icon database and store the icons in the given path. * @param path The directory path where the icon database will be stored. */ - public void open(String path) { - throw new MustOverrideException(); - } + public abstract void open(String path); /** * Close the shared instance of the icon database. */ - public void close() { - throw new MustOverrideException(); - } + public abstract void close(); /** * Removes all the icons in the database. */ - public void removeAllIcons() { - throw new MustOverrideException(); - } + public abstract void removeAllIcons(); /** * Request the Bitmap representing the icon for the given page @@ -76,32 +71,25 @@ public class WebIconDatabase { * @param url The page's url. * @param listener An implementation on IconListener to receive the result. */ - public void requestIconForPageUrl(String url, IconListener listener) { - throw new MustOverrideException(); - } + public abstract void requestIconForPageUrl(String url, IconListener listener); /** {@hide} */ - public void bulkRequestIconForPageUrl(ContentResolver cr, String where, - IconListener listener) { - throw new MustOverrideException(); - } + @SystemApi + public abstract void bulkRequestIconForPageUrl(ContentResolver cr, String where, + IconListener listener); /** * Retain the icon for the given page url. * @param url The page's url. */ - public void retainIconForPageUrl(String url) { - throw new MustOverrideException(); - } + public abstract void retainIconForPageUrl(String url); /** * Release the icon for the given page url. * @param url The page's url. */ - public void releaseIconForPageUrl(String url) { - throw new MustOverrideException(); - } + public abstract void releaseIconForPageUrl(String url); /** * Get the global instance of WebIconDatabase. @@ -113,9 +101,4 @@ public class WebIconDatabase { // XXX: Must be created in the UI thread. return WebViewFactory.getProvider().getWebIconDatabase(); } - - /** - * @hide Only for use by WebViewProvider implementations - */ - protected WebIconDatabase() {} } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 7cf3cb5..1d2c311 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -204,25 +204,15 @@ public abstract class WebSettings { public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2; /** - * Hidden constructor to prevent clients from creating a new settings - * instance or deriving the class. - * - * @hide - */ - protected WebSettings() { - } - - /** * Enables dumping the pages navigation cache to a text file. The default * is false. * * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SystemApi @Deprecated - public void setNavDump(boolean enabled) { - throw new MustOverrideException(); - } + public abstract void setNavDump(boolean enabled); /** * Gets whether dumping the navigation cache is enabled. @@ -232,10 +222,9 @@ public abstract class WebSettings { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SystemApi @Deprecated - public boolean getNavDump() { - throw new MustOverrideException(); - } + public abstract boolean getNavDump(); /** * Sets whether the WebView should support zooming using its on-screen zoom @@ -246,9 +235,7 @@ public abstract class WebSettings { * * @param support whether the WebView should support zoom */ - public void setSupportZoom(boolean support) { - throw new MustOverrideException(); - } + public abstract void setSupportZoom(boolean support); /** * Gets whether the WebView supports zoom. @@ -256,9 +243,7 @@ public abstract class WebSettings { * @return true if the WebView supports zoom * @see #setSupportZoom */ - public boolean supportZoom() { - throw new MustOverrideException(); - } + public abstract boolean supportZoom(); /** * Sets whether the WebView requires a user gesture to play media. @@ -266,9 +251,7 @@ public abstract class WebSettings { * * @param require whether the WebView requires a user gesture to play media */ - public void setMediaPlaybackRequiresUserGesture(boolean require) { - throw new MustOverrideException(); - } + public abstract void setMediaPlaybackRequiresUserGesture(boolean require); /** * Gets whether the WebView requires a user gesture to play media. @@ -276,9 +259,7 @@ public abstract class WebSettings { * @return true if the WebView requires a user gesture to play media * @see #setMediaPlaybackRequiresUserGesture */ - public boolean getMediaPlaybackRequiresUserGesture() { - throw new MustOverrideException(); - } + public abstract boolean getMediaPlaybackRequiresUserGesture(); /** * Sets whether the WebView should use its built-in zoom mechanisms. The @@ -295,9 +276,7 @@ public abstract class WebSettings { // This method was intended to select between the built-in zoom mechanisms // and the separate zoom controls. The latter were obtained using // {@link WebView#getZoomControls}, which is now hidden. - public void setBuiltInZoomControls(boolean enabled) { - throw new MustOverrideException(); - } + public abstract void setBuiltInZoomControls(boolean enabled); /** * Gets whether the zoom mechanisms built into WebView are being used. @@ -305,9 +284,7 @@ public abstract class WebSettings { * @return true if the zoom mechanisms built into WebView are being used * @see #setBuiltInZoomControls */ - public boolean getBuiltInZoomControls() { - throw new MustOverrideException(); - } + public abstract boolean getBuiltInZoomControls(); /** * Sets whether the WebView should display on-screen zoom controls when @@ -316,9 +293,7 @@ public abstract class WebSettings { * * @param enabled whether the WebView should display on-screen zoom controls */ - public void setDisplayZoomControls(boolean enabled) { - throw new MustOverrideException(); - } + public abstract void setDisplayZoomControls(boolean enabled); /** * Gets whether the WebView displays on-screen zoom controls when using @@ -328,9 +303,7 @@ public abstract class WebSettings { * the built-in zoom mechanisms * @see #setDisplayZoomControls */ - public boolean getDisplayZoomControls() { - throw new MustOverrideException(); - } + public abstract boolean getDisplayZoomControls(); /** * Enables or disables file access within WebView. File access is enabled by @@ -338,36 +311,28 @@ public abstract class WebSettings { * Assets and resources are still accessible using file:///android_asset and * file:///android_res. */ - public void setAllowFileAccess(boolean allow) { - throw new MustOverrideException(); - } + public abstract void setAllowFileAccess(boolean allow); /** * Gets whether this WebView supports file access. * * @see #setAllowFileAccess */ - public boolean getAllowFileAccess() { - throw new MustOverrideException(); - } + public abstract boolean getAllowFileAccess(); /** * Enables or disables content URL access within WebView. Content URL * access allows WebView to load content from a content provider installed * in the system. The default is enabled. */ - public void setAllowContentAccess(boolean allow) { - throw new MustOverrideException(); - } + public abstract void setAllowContentAccess(boolean allow); /** * Gets whether this WebView supports content URL access. * * @see #setAllowContentAccess */ - public boolean getAllowContentAccess() { - throw new MustOverrideException(); - } + public abstract boolean getAllowContentAccess(); /** * Sets whether the WebView loads pages in overview mode, that is, @@ -376,9 +341,7 @@ public abstract class WebSettings { * of the WebView control, for example, when {@link #getUseWideViewPort} * is enabled. The default is false. */ - public void setLoadWithOverviewMode(boolean overview) { - throw new MustOverrideException(); - } + public abstract void setLoadWithOverviewMode(boolean overview); /** * Gets whether this WebView loads pages in overview mode. @@ -386,9 +349,7 @@ public abstract class WebSettings { * @return whether this WebView loads pages in overview mode * @see #setLoadWithOverviewMode */ - public boolean getLoadWithOverviewMode() { - throw new MustOverrideException(); - } + public abstract boolean getLoadWithOverviewMode(); /** * Sets whether the WebView will enable smooth transition while panning or @@ -400,9 +361,7 @@ public abstract class WebSettings { * @deprecated This method is now obsolete, and will become a no-op in future. */ @Deprecated - public void setEnableSmoothTransition(boolean enable) { - throw new MustOverrideException(); - } + public abstract void setEnableSmoothTransition(boolean enable); /** * Gets whether the WebView enables smooth transition while panning or @@ -413,9 +372,7 @@ public abstract class WebSettings { * @deprecated This method is now obsolete, and will become a no-op in future. */ @Deprecated - public boolean enableSmoothTransition() { - throw new MustOverrideException(); - } + public abstract boolean enableSmoothTransition(); /** * Sets whether the WebView uses its background for over scroll background. @@ -425,10 +382,9 @@ public abstract class WebSettings { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SystemApi @Deprecated - public void setUseWebViewBackgroundForOverscrollBackground(boolean view) { - throw new MustOverrideException(); - } + public abstract void setUseWebViewBackgroundForOverscrollBackground(boolean view); /** * Gets whether this WebView uses WebView's background instead of @@ -438,17 +394,14 @@ public abstract class WebSettings { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SystemApi @Deprecated - public boolean getUseWebViewBackgroundForOverscrollBackground() { - throw new MustOverrideException(); - } + public abstract boolean getUseWebViewBackgroundForOverscrollBackground(); /** * Sets whether the WebView should save form data. The default is true. */ - public void setSaveFormData(boolean save) { - throw new MustOverrideException(); - } + public abstract void setSaveFormData(boolean save); /** * Gets whether the WebView saves form data. @@ -456,18 +409,14 @@ public abstract class WebSettings { * @return whether the WebView saves form data * @see #setSaveFormData */ - public boolean getSaveFormData() { - throw new MustOverrideException(); - } + public abstract boolean getSaveFormData(); /** * Sets whether the WebView should save passwords. The default is true. * @deprecated Saving passwords in WebView will not be supported in future versions. */ @Deprecated - public void setSavePassword(boolean save) { - throw new MustOverrideException(); - } + public abstract void setSavePassword(boolean save); /** * Gets whether the WebView saves passwords. @@ -477,18 +426,14 @@ public abstract class WebSettings { * @deprecated Saving passwords in WebView will not be supported in future versions. */ @Deprecated - public boolean getSavePassword() { - throw new MustOverrideException(); - } + public abstract boolean getSavePassword(); /** * Sets the text zoom of the page in percent. The default is 100. * * @param textZoom the text zoom in percent */ - public synchronized void setTextZoom(int textZoom) { - throw new MustOverrideException(); - } + public abstract void setTextZoom(int textZoom); /** * Gets the text zoom of the page in percent. @@ -496,27 +441,23 @@ public abstract class WebSettings { * @return the text zoom of the page in percent * @see #setTextZoom */ - public synchronized int getTextZoom() { - throw new MustOverrideException(); - } + public abstract int getTextZoom(); /** * Sets policy for third party cookies. * Developers should access this via {@link CookieManager#setShouldAcceptThirdPartyCookies}. * @hide Internal API. */ - public void setAcceptThirdPartyCookies(boolean accept) { - throw new MustOverrideException(); - } + @SystemApi + public abstract void setAcceptThirdPartyCookies(boolean accept); /** * Gets policy for third party cookies. * Developers should access this via {@link CookieManager#getShouldAcceptThirdPartyCookies}. * @hide Internal API */ - public boolean getAcceptThirdPartyCookies() { - throw new MustOverrideException(); - } + @SystemApi + public abstract boolean getAcceptThirdPartyCookies(); /** * Sets the text size of the page. The default is {@link TextSize#NORMAL}. @@ -569,9 +510,7 @@ public abstract class WebSettings { * recommended alternatives. */ @Deprecated - public void setDefaultZoom(ZoomDensity zoom) { - throw new MustOverrideException(); - } + public abstract void setDefaultZoom(ZoomDensity zoom); /** * Gets the default zoom density of the page. This should be called from @@ -583,9 +522,7 @@ public abstract class WebSettings { * @see #setDefaultZoom * @deprecated Will only return the default value. */ - public ZoomDensity getDefaultZoom() { - throw new MustOverrideException(); - } + public abstract ZoomDensity getDefaultZoom(); /** * Enables using light touches to make a selection and activate mouseovers. @@ -593,9 +530,7 @@ public abstract class WebSettings { * setting is obsolete and has no effect. */ @Deprecated - public void setLightTouchEnabled(boolean enabled) { - throw new MustOverrideException(); - } + public abstract void setLightTouchEnabled(boolean enabled); /** * Gets whether light touches are enabled. @@ -603,9 +538,7 @@ public abstract class WebSettings { * @deprecated This setting is obsolete. */ @Deprecated - public boolean getLightTouchEnabled() { - throw new MustOverrideException(); - } + public abstract boolean getLightTouchEnabled(); /** * Controlled a rendering optimization that is no longer present. Setting @@ -615,7 +548,7 @@ public abstract class WebSettings { * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated - public synchronized void setUseDoubleTree(boolean use) { + public void setUseDoubleTree(boolean use) { // Specified to do nothing, so no need for derived classes to override. } @@ -627,7 +560,7 @@ public abstract class WebSettings { * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated - public synchronized boolean getUseDoubleTree() { + public boolean getUseDoubleTree() { // Returns false unconditionally, so no need for derived classes to override. return false; } @@ -645,10 +578,9 @@ public abstract class WebSettings { * @deprecated Please use {@link #setUserAgentString} instead. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SystemApi @Deprecated - public synchronized void setUserAgent(int ua) { - throw new MustOverrideException(); - } + public abstract void setUserAgent(int ua); /** * Gets the user-agent as an integer code. @@ -664,10 +596,9 @@ public abstract class WebSettings { * @deprecated Please use {@link #getUserAgentString} instead. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SystemApi @Deprecated - public synchronized int getUserAgent() { - throw new MustOverrideException(); - } + public abstract int getUserAgent(); /** * Sets whether the WebView should enable support for the "viewport" @@ -680,9 +611,7 @@ public abstract class WebSettings { * * @param use whether to enable support for the viewport meta tag */ - public synchronized void setUseWideViewPort(boolean use) { - throw new MustOverrideException(); - } + public abstract void setUseWideViewPort(boolean use); /** * Gets whether the WebView supports the "viewport" @@ -691,9 +620,7 @@ public abstract class WebSettings { * @return true if the WebView supports the viewport meta tag * @see #setUseWideViewPort */ - public synchronized boolean getUseWideViewPort() { - throw new MustOverrideException(); - } + public abstract boolean getUseWideViewPort(); /** * Sets whether the WebView whether supports multiple windows. If set to @@ -702,9 +629,7 @@ public abstract class WebSettings { * * @param support whether to suport multiple windows */ - public synchronized void setSupportMultipleWindows(boolean support) { - throw new MustOverrideException(); - } + public abstract void setSupportMultipleWindows(boolean support); /** * Gets whether the WebView supports multiple windows. @@ -712,9 +637,7 @@ public abstract class WebSettings { * @return true if the WebView supports multiple windows * @see #setSupportMultipleWindows */ - public synchronized boolean supportMultipleWindows() { - throw new MustOverrideException(); - } + public abstract boolean supportMultipleWindows(); /** * Sets the underlying layout algorithm. This will cause a relayout of the @@ -722,9 +645,7 @@ public abstract class WebSettings { * * @param l the layout algorithm to use, as a {@link LayoutAlgorithm} value */ - public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) { - throw new MustOverrideException(); - } + public abstract void setLayoutAlgorithm(LayoutAlgorithm l); /** * Gets the current layout algorithm. @@ -732,18 +653,14 @@ public abstract class WebSettings { * @return the layout algorithm in use, as a {@link LayoutAlgorithm} value * @see #setLayoutAlgorithm */ - public synchronized LayoutAlgorithm getLayoutAlgorithm() { - throw new MustOverrideException(); - } + public abstract LayoutAlgorithm getLayoutAlgorithm(); /** * Sets the standard font family name. The default is "sans-serif". * * @param font a font family name */ - public synchronized void setStandardFontFamily(String font) { - throw new MustOverrideException(); - } + public abstract void setStandardFontFamily(String font); /** * Gets the standard font family name. @@ -751,18 +668,14 @@ public abstract class WebSettings { * @return the standard font family name as a string * @see #setStandardFontFamily */ - public synchronized String getStandardFontFamily() { - throw new MustOverrideException(); - } + public abstract String getStandardFontFamily(); /** * Sets the fixed font family name. The default is "monospace". * * @param font a font family name */ - public synchronized void setFixedFontFamily(String font) { - throw new MustOverrideException(); - } + public abstract void setFixedFontFamily(String font); /** * Gets the fixed font family name. @@ -770,18 +683,14 @@ public abstract class WebSettings { * @return the fixed font family name as a string * @see #setFixedFontFamily */ - public synchronized String getFixedFontFamily() { - throw new MustOverrideException(); - } + public abstract String getFixedFontFamily(); /** * Sets the sans-serif font family name. The default is "sans-serif". * * @param font a font family name */ - public synchronized void setSansSerifFontFamily(String font) { - throw new MustOverrideException(); - } + public abstract void setSansSerifFontFamily(String font); /** * Gets the sans-serif font family name. @@ -789,18 +698,14 @@ public abstract class WebSettings { * @return the sans-serif font family name as a string * @see #setSansSerifFontFamily */ - public synchronized String getSansSerifFontFamily() { - throw new MustOverrideException(); - } + public abstract String getSansSerifFontFamily(); /** * Sets the serif font family name. The default is "sans-serif". * * @param font a font family name */ - public synchronized void setSerifFontFamily(String font) { - throw new MustOverrideException(); - } + public abstract void setSerifFontFamily(String font); /** * Gets the serif font family name. The default is "serif". @@ -808,18 +713,14 @@ public abstract class WebSettings { * @return the serif font family name as a string * @see #setSerifFontFamily */ - public synchronized String getSerifFontFamily() { - throw new MustOverrideException(); - } + public abstract String getSerifFontFamily(); /** * Sets the cursive font family name. The default is "cursive". * * @param font a font family name */ - public synchronized void setCursiveFontFamily(String font) { - throw new MustOverrideException(); - } + public abstract void setCursiveFontFamily(String font); /** * Gets the cursive font family name. @@ -827,18 +728,14 @@ public abstract class WebSettings { * @return the cursive font family name as a string * @see #setCursiveFontFamily */ - public synchronized String getCursiveFontFamily() { - throw new MustOverrideException(); - } + public abstract String getCursiveFontFamily(); /** * Sets the fantasy font family name. The default is "fantasy". * * @param font a font family name */ - public synchronized void setFantasyFontFamily(String font) { - throw new MustOverrideException(); - } + public abstract void setFantasyFontFamily(String font); /** * Gets the fantasy font family name. @@ -846,9 +743,7 @@ public abstract class WebSettings { * @return the fantasy font family name as a string * @see #setFantasyFontFamily */ - public synchronized String getFantasyFontFamily() { - throw new MustOverrideException(); - } + public abstract String getFantasyFontFamily(); /** * Sets the minimum font size. The default is 8. @@ -856,9 +751,7 @@ public abstract class WebSettings { * @param size a non-negative integer between 1 and 72. Any number outside * the specified range will be pinned. */ - public synchronized void setMinimumFontSize(int size) { - throw new MustOverrideException(); - } + public abstract void setMinimumFontSize(int size); /** * Gets the minimum font size. @@ -866,9 +759,7 @@ public abstract class WebSettings { * @return a non-negative integer between 1 and 72 * @see #setMinimumFontSize */ - public synchronized int getMinimumFontSize() { - throw new MustOverrideException(); - } + public abstract int getMinimumFontSize(); /** * Sets the minimum logical font size. The default is 8. @@ -876,9 +767,7 @@ public abstract class WebSettings { * @param size a non-negative integer between 1 and 72. Any number outside * the specified range will be pinned. */ - public synchronized void setMinimumLogicalFontSize(int size) { - throw new MustOverrideException(); - } + public abstract void setMinimumLogicalFontSize(int size); /** * Gets the minimum logical font size. @@ -886,9 +775,7 @@ public abstract class WebSettings { * @return a non-negative integer between 1 and 72 * @see #setMinimumLogicalFontSize */ - public synchronized int getMinimumLogicalFontSize() { - throw new MustOverrideException(); - } + public abstract int getMinimumLogicalFontSize(); /** * Sets the default font size. The default is 16. @@ -896,9 +783,7 @@ public abstract class WebSettings { * @param size a non-negative integer between 1 and 72. Any number outside * the specified range will be pinned. */ - public synchronized void setDefaultFontSize(int size) { - throw new MustOverrideException(); - } + public abstract void setDefaultFontSize(int size); /** * Gets the default font size. @@ -906,9 +791,7 @@ public abstract class WebSettings { * @return a non-negative integer between 1 and 72 * @see #setDefaultFontSize */ - public synchronized int getDefaultFontSize() { - throw new MustOverrideException(); - } + public abstract int getDefaultFontSize(); /** * Sets the default fixed font size. The default is 16. @@ -916,9 +799,7 @@ public abstract class WebSettings { * @param size a non-negative integer between 1 and 72. Any number outside * the specified range will be pinned. */ - public synchronized void setDefaultFixedFontSize(int size) { - throw new MustOverrideException(); - } + public abstract void setDefaultFixedFontSize(int size); /** * Gets the default fixed font size. @@ -926,9 +807,7 @@ public abstract class WebSettings { * @return a non-negative integer between 1 and 72 * @see #setDefaultFixedFontSize */ - public synchronized int getDefaultFixedFontSize() { - throw new MustOverrideException(); - } + public abstract int getDefaultFixedFontSize(); /** * Sets whether the WebView should load image resources. Note that this method @@ -941,9 +820,7 @@ public abstract class WebSettings { * * @param flag whether the WebView should load image resources */ - public synchronized void setLoadsImagesAutomatically(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setLoadsImagesAutomatically(boolean flag); /** * Gets whether the WebView loads image resources. This includes @@ -952,9 +829,7 @@ public abstract class WebSettings { * @return true if the WebView loads image resources * @see #setLoadsImagesAutomatically */ - public synchronized boolean getLoadsImagesAutomatically() { - throw new MustOverrideException(); - } + public abstract boolean getLoadsImagesAutomatically(); /** * Sets whether the WebView should not load image resources from the @@ -971,9 +846,7 @@ public abstract class WebSettings { * network * @see #setBlockNetworkLoads */ - public synchronized void setBlockNetworkImage(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setBlockNetworkImage(boolean flag); /** * Gets whether the WebView does not load image resources from the network. @@ -981,9 +854,7 @@ public abstract class WebSettings { * @return true if the WebView does not load image resources from the network * @see #setBlockNetworkImage */ - public synchronized boolean getBlockNetworkImage() { - throw new MustOverrideException(); - } + public abstract boolean getBlockNetworkImage(); /** * Sets whether the WebView should not load resources from the network. @@ -1003,9 +874,7 @@ public abstract class WebSettings { * network * @see android.webkit.WebView#reload */ - public synchronized void setBlockNetworkLoads(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setBlockNetworkLoads(boolean flag); /** * Gets whether the WebView does not load any resources from the network. @@ -1013,9 +882,7 @@ public abstract class WebSettings { * @return true if the WebView does not load any resources from the network * @see #setBlockNetworkLoads */ - public synchronized boolean getBlockNetworkLoads() { - throw new MustOverrideException(); - } + public abstract boolean getBlockNetworkLoads(); /** * Tells the WebView to enable JavaScript execution. @@ -1023,9 +890,7 @@ public abstract class WebSettings { * * @param flag true if the WebView should execute JavaScript */ - public synchronized void setJavaScriptEnabled(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setJavaScriptEnabled(boolean flag); /** * Sets whether JavaScript running in the context of a file scheme URL @@ -1076,10 +941,9 @@ public abstract class WebSettings { * {@link #setPluginState} * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} */ + @SystemApi @Deprecated - public synchronized void setPluginsEnabled(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setPluginsEnabled(boolean flag); /** * Tells the WebView to enable, disable, or have plugins on demand. On @@ -1092,9 +956,7 @@ public abstract class WebSettings { * @deprecated Plugins will not be supported in future, and should not be used. */ @Deprecated - public synchronized void setPluginState(PluginState state) { - throw new MustOverrideException(); - } + public abstract void setPluginState(PluginState state); /** * Sets a custom path to plugins used by the WebView. This method is @@ -1106,7 +968,7 @@ public abstract class WebSettings { * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} */ @Deprecated - public synchronized void setPluginsPath(String pluginsPath) { + public void setPluginsPath(String pluginsPath) { // Specified to do nothing, so no need for derived classes to override. } @@ -1125,9 +987,7 @@ public abstract class WebSettings { // Note that the WebCore Database Tracker only allows the path to be set // once. @Deprecated - public synchronized void setDatabasePath(String databasePath) { - throw new MustOverrideException(); - } + public abstract void setDatabasePath(String databasePath); /** * Sets the path where the Geolocation databases should be saved. In order @@ -1138,9 +998,7 @@ public abstract class WebSettings { * saved. */ // This will update WebCore when the Sync runs in the C++ side. - public synchronized void setGeolocationDatabasePath(String databasePath) { - throw new MustOverrideException(); - } + public abstract void setGeolocationDatabasePath(String databasePath); /** * Sets whether the Application Caches API should be enabled. The default @@ -1150,9 +1008,7 @@ public abstract class WebSettings { * * @param flag true if the WebView should enable Application Caches */ - public synchronized void setAppCacheEnabled(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setAppCacheEnabled(boolean flag); /** * Sets the path to the Application Caches files. In order for the @@ -1164,9 +1020,7 @@ public abstract class WebSettings { * Application Caches files. * @see #setAppCacheEnabled */ - public synchronized void setAppCachePath(String appCachePath) { - throw new MustOverrideException(); - } + public abstract void setAppCachePath(String appCachePath); /** * Sets the maximum size for the Application Cache content. The passed size @@ -1180,9 +1034,7 @@ public abstract class WebSettings { * @deprecated In future quota will be managed automatically. */ @Deprecated - public synchronized void setAppCacheMaxSize(long appCacheMaxSize) { - throw new MustOverrideException(); - } + public abstract void setAppCacheMaxSize(long appCacheMaxSize); /** * Sets whether the database storage API is enabled. The default value is @@ -1196,18 +1048,14 @@ public abstract class WebSettings { * * @param flag true if the WebView should use the database storage API */ - public synchronized void setDatabaseEnabled(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setDatabaseEnabled(boolean flag); /** * Sets whether the DOM storage API is enabled. The default value is false. * * @param flag true if the WebView should use the DOM storage API */ - public synchronized void setDomStorageEnabled(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setDomStorageEnabled(boolean flag); /** * Gets whether the DOM Storage APIs are enabled. @@ -1215,9 +1063,8 @@ public abstract class WebSettings { * @return true if the DOM Storage APIs are enabled * @see #setDomStorageEnabled */ - public synchronized boolean getDomStorageEnabled() { - throw new MustOverrideException(); - } + public abstract boolean getDomStorageEnabled(); + /** * Gets the path to where database storage API databases are saved. * @@ -1226,9 +1073,7 @@ public abstract class WebSettings { * @deprecated Database paths are managed by the implementation this method is obsolete. */ @Deprecated - public synchronized String getDatabasePath() { - throw new MustOverrideException(); - } + public abstract String getDatabasePath(); /** * Gets whether the database storage API is enabled. @@ -1236,9 +1081,7 @@ public abstract class WebSettings { * @return true if the database storage API is enabled * @see #setDatabaseEnabled */ - public synchronized boolean getDatabaseEnabled() { - throw new MustOverrideException(); - } + public abstract boolean getDatabaseEnabled(); /** * Sets whether Geolocation is enabled. The default is true. @@ -1260,9 +1103,7 @@ public abstract class WebSettings { * * @param flag whether Geolocation should be enabled */ - public synchronized void setGeolocationEnabled(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setGeolocationEnabled(boolean flag); /** * Gets whether JavaScript is enabled. @@ -1270,9 +1111,7 @@ public abstract class WebSettings { * @return true if JavaScript is enabled * @see #setJavaScriptEnabled */ - public synchronized boolean getJavaScriptEnabled() { - throw new MustOverrideException(); - } + public abstract boolean getJavaScriptEnabled(); /** * Gets whether JavaScript running in the context of a file scheme URL can @@ -1303,10 +1142,9 @@ public abstract class WebSettings { * @deprecated This method has been replaced by {@link #getPluginState} * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} */ + @SystemApi @Deprecated - public synchronized boolean getPluginsEnabled() { - throw new MustOverrideException(); - } + public abstract boolean getPluginsEnabled(); /** * Gets the current state regarding whether plugins are enabled. @@ -1316,9 +1154,7 @@ public abstract class WebSettings { * @deprecated Plugins will not be supported in future, and should not be used. */ @Deprecated - public synchronized PluginState getPluginState() { - throw new MustOverrideException(); - } + public abstract PluginState getPluginState(); /** * Gets the directory that contains the plugin libraries. This method is @@ -1330,7 +1166,7 @@ public abstract class WebSettings { * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} */ @Deprecated - public synchronized String getPluginsPath() { + public String getPluginsPath() { // Unconditionally returns empty string, so no need for derived classes to override. return ""; } @@ -1341,9 +1177,7 @@ public abstract class WebSettings { * * @param flag true if JavaScript can open windows automatically */ - public synchronized void setJavaScriptCanOpenWindowsAutomatically(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean flag); /** * Gets whether JavaScript can open windows automatically. @@ -1352,9 +1186,7 @@ public abstract class WebSettings { * window.open() * @see #setJavaScriptCanOpenWindowsAutomatically */ - public synchronized boolean getJavaScriptCanOpenWindowsAutomatically() { - throw new MustOverrideException(); - } + public abstract boolean getJavaScriptCanOpenWindowsAutomatically(); /** * Sets the default text encoding name to use when decoding html pages. @@ -1362,9 +1194,7 @@ public abstract class WebSettings { * * @param encoding the text encoding name */ - public synchronized void setDefaultTextEncodingName(String encoding) { - throw new MustOverrideException(); - } + public abstract void setDefaultTextEncodingName(String encoding); /** * Gets the default text encoding name. @@ -1372,17 +1202,13 @@ public abstract class WebSettings { * @return the default text encoding name as a string * @see #setDefaultTextEncodingName */ - public synchronized String getDefaultTextEncodingName() { - throw new MustOverrideException(); - } + public abstract String getDefaultTextEncodingName(); /** * Sets the WebView's user-agent string. If the string is null or empty, * the system default value will be used. */ - public synchronized void setUserAgentString(String ua) { - throw new MustOverrideException(); - } + public abstract void setUserAgentString(String ua); /** * Gets the WebView's user-agent string. @@ -1390,9 +1216,7 @@ public abstract class WebSettings { * @return the WebView's user-agent string * @see #setUserAgentString */ - public synchronized String getUserAgentString() { - throw new MustOverrideException(); - } + public abstract String getUserAgentString(); /** * Returns the default User-Agent used by a WebView. @@ -1412,9 +1236,7 @@ public abstract class WebSettings { * * @param flag whether the WebView needs to set a node */ - public void setNeedInitialFocus(boolean flag) { - throw new MustOverrideException(); - } + public abstract void setNeedInitialFocus(boolean flag); /** * Sets the priority of the Render thread. Unlike the other settings, this @@ -1426,9 +1248,7 @@ public abstract class WebSettings { * not be supported in future versions. */ @Deprecated - public synchronized void setRenderPriority(RenderPriority priority) { - throw new MustOverrideException(); - } + public abstract void setRenderPriority(RenderPriority priority); /** * Overrides the way the cache is used. The way the cache is used is based @@ -1442,9 +1262,7 @@ public abstract class WebSettings { * * @param mode the mode to use */ - public void setCacheMode(int mode) { - throw new MustOverrideException(); - } + public abstract void setCacheMode(int mode); /** * Gets the current setting for overriding the cache mode. @@ -1452,9 +1270,7 @@ public abstract class WebSettings { * @return the current setting for overriding the cache mode * @see #setCacheMode */ - public int getCacheMode() { - throw new MustOverrideException(); - } + public abstract int getCacheMode(); /** * Configures the WebView's behavior when a secure origin attempts to load a resource from an diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java index 3bfe9cf..947d0cb 100644 --- a/core/java/android/webkit/WebStorage.java +++ b/core/java/android/webkit/WebStorage.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.SystemApi; + import java.util.Map; /** @@ -65,23 +67,13 @@ public class WebStorage { private long mUsage = 0; /** @hide */ + @SystemApi protected Origin(String origin, long quota, long usage) { mOrigin = origin; mQuota = quota; mUsage = usage; } - /** @hide */ - protected Origin(String origin, long quota) { - mOrigin = origin; - mQuota = quota; - } - - /** @hide */ - protected Origin(String origin) { - mOrigin = origin; - } - /** * Gets the string representation of this origin. * @@ -210,5 +202,6 @@ public class WebStorage { * way to call createHandler() and createUIHandler(), so it would not work). * @hide */ + @SystemApi public WebStorage() {} } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 592d6e2..6793634 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.annotation.Widget; import android.content.Context; import android.content.res.Configuration; @@ -256,10 +257,12 @@ public class WebView extends AbsoluteLayout * always stay as a hidden API. * @hide */ + @SystemApi public static final String DATA_REDUCTION_PROXY_SETTING_CHANGED = "android.webkit.DATA_REDUCTION_PROXY_SETTING_CHANGED"; private static final String LOGTAG = "WebView"; + private static final boolean TRACE = false; // Throwing an exception for incorrect thread usage if the // build target is JB MR2 or newer. Defaults to false, and is @@ -394,6 +397,7 @@ public class WebView extends AbsoluteLayout /** * @hide Only for use by WebViewProvider implementations */ + @SystemApi public HitTestResult() { mType = UNKNOWN_TYPE; } @@ -401,6 +405,7 @@ public class WebView extends AbsoluteLayout /** * @hide Only for use by WebViewProvider implementations */ + @SystemApi public void setType(int type) { mType = type; } @@ -408,6 +413,7 @@ public class WebView extends AbsoluteLayout /** * @hide Only for use by WebViewProvider implementations */ + @SystemApi public void setExtra(String extra) { mExtra = extra; } @@ -542,7 +548,7 @@ public class WebView extends AbsoluteLayout sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2; checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "WebView<init>"); + if (TRACE) Log.d(LOGTAG, "WebView<init>"); ensureProviderCreated(); mProvider.init(javaScriptInterfaces, privateBrowsing); @@ -557,7 +563,7 @@ public class WebView extends AbsoluteLayout */ public void setHorizontalScrollbarOverlay(boolean overlay) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setHorizontalScrollbarOverlay=" + overlay); + if (TRACE) Log.d(LOGTAG, "setHorizontalScrollbarOverlay=" + overlay); mProvider.setHorizontalScrollbarOverlay(overlay); } @@ -568,7 +574,7 @@ public class WebView extends AbsoluteLayout */ public void setVerticalScrollbarOverlay(boolean overlay) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setVerticalScrollbarOverlay=" + overlay); + if (TRACE) Log.d(LOGTAG, "setVerticalScrollbarOverlay=" + overlay); mProvider.setVerticalScrollbarOverlay(overlay); } @@ -623,7 +629,7 @@ public class WebView extends AbsoluteLayout @Deprecated public void setCertificate(SslCertificate certificate) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setCertificate=" + certificate); + if (TRACE) Log.d(LOGTAG, "setCertificate=" + certificate); mProvider.setCertificate(certificate); } @@ -647,7 +653,7 @@ public class WebView extends AbsoluteLayout @Deprecated public void savePassword(String host, String username, String password) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "savePassword=" + host); + if (TRACE) Log.d(LOGTAG, "savePassword=" + host); mProvider.savePassword(host, username, password); } @@ -667,7 +673,7 @@ public class WebView extends AbsoluteLayout public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setHttpAuthUsernamePassword=" + host); + if (TRACE) Log.d(LOGTAG, "setHttpAuthUsernamePassword=" + host); mProvider.setHttpAuthUsernamePassword(host, realm, username, password); } @@ -697,7 +703,7 @@ public class WebView extends AbsoluteLayout */ public void destroy() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "destroy"); + if (TRACE) Log.d(LOGTAG, "destroy"); mProvider.destroy(); } @@ -743,7 +749,7 @@ public class WebView extends AbsoluteLayout */ public void setNetworkAvailable(boolean networkUp) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setNetworkAvailable=" + networkUp); + if (TRACE) Log.d(LOGTAG, "setNetworkAvailable=" + networkUp); mProvider.setNetworkAvailable(networkUp); } @@ -760,7 +766,7 @@ public class WebView extends AbsoluteLayout */ public WebBackForwardList saveState(Bundle outState) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "saveState"); + if (TRACE) Log.d(LOGTAG, "saveState"); return mProvider.saveState(outState); } @@ -777,7 +783,7 @@ public class WebView extends AbsoluteLayout @Deprecated public boolean savePicture(Bundle b, final File dest) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "savePicture=" + dest.getName()); + if (TRACE) Log.d(LOGTAG, "savePicture=" + dest.getName()); return mProvider.savePicture(b, dest); } @@ -795,7 +801,7 @@ public class WebView extends AbsoluteLayout @Deprecated public boolean restorePicture(Bundle b, File src) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "restorePicture=" + src.getName()); + if (TRACE) Log.d(LOGTAG, "restorePicture=" + src.getName()); return mProvider.restorePicture(b, src); } @@ -813,7 +819,7 @@ public class WebView extends AbsoluteLayout */ public WebBackForwardList restoreState(Bundle inState) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "restoreState"); + if (TRACE) Log.d(LOGTAG, "restoreState"); return mProvider.restoreState(inState); } @@ -830,7 +836,7 @@ public class WebView extends AbsoluteLayout */ public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { checkThread(); - if (DebugFlags.TRACE_API) { + if (TRACE) { StringBuilder headers = new StringBuilder(); if (additionalHttpHeaders != null) { for (Map.Entry<String, String> entry : additionalHttpHeaders.entrySet()) { @@ -849,7 +855,7 @@ public class WebView extends AbsoluteLayout */ public void loadUrl(String url) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadUrl=" + url); + if (TRACE) Log.d(LOGTAG, "loadUrl=" + url); mProvider.loadUrl(url); } @@ -864,7 +870,7 @@ public class WebView extends AbsoluteLayout */ public void postUrl(String url, byte[] postData) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "postUrl=" + url); + if (TRACE) Log.d(LOGTAG, "postUrl=" + url); if (URLUtil.isNetworkUrl(url)) { mProvider.postUrl(url, postData); } else { @@ -903,7 +909,7 @@ public class WebView extends AbsoluteLayout */ public void loadData(String data, String mimeType, String encoding) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadData"); + if (TRACE) Log.d(LOGTAG, "loadData"); mProvider.loadData(data, mimeType, encoding); } @@ -936,7 +942,7 @@ public class WebView extends AbsoluteLayout public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadDataWithBaseURL=" + baseUrl); + if (TRACE) Log.d(LOGTAG, "loadDataWithBaseURL=" + baseUrl); mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } @@ -953,7 +959,7 @@ public class WebView extends AbsoluteLayout */ public void evaluateJavascript(String script, ValueCallback<String> resultCallback) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "evaluateJavascript=" + script); + if (TRACE) Log.d(LOGTAG, "evaluateJavascript=" + script); mProvider.evaluateJavaScript(script, resultCallback); } @@ -964,7 +970,7 @@ public class WebView extends AbsoluteLayout */ public void saveWebArchive(String filename) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "saveWebArchive=" + filename); + if (TRACE) Log.d(LOGTAG, "saveWebArchive=" + filename); mProvider.saveWebArchive(filename); } @@ -982,7 +988,7 @@ public class WebView extends AbsoluteLayout */ public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "saveWebArchive(auto)=" + basename); + if (TRACE) Log.d(LOGTAG, "saveWebArchive(auto)=" + basename); mProvider.saveWebArchive(basename, autoname, callback); } @@ -991,7 +997,7 @@ public class WebView extends AbsoluteLayout */ public void stopLoading() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "stopLoading"); + if (TRACE) Log.d(LOGTAG, "stopLoading"); mProvider.stopLoading(); } @@ -1000,7 +1006,7 @@ public class WebView extends AbsoluteLayout */ public void reload() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "reload"); + if (TRACE) Log.d(LOGTAG, "reload"); mProvider.reload(); } @@ -1019,7 +1025,7 @@ public class WebView extends AbsoluteLayout */ public void goBack() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "goBack"); + if (TRACE) Log.d(LOGTAG, "goBack"); mProvider.goBack(); } @@ -1038,7 +1044,7 @@ public class WebView extends AbsoluteLayout */ public void goForward() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "goForward"); + if (TRACE) Log.d(LOGTAG, "goForward"); mProvider.goForward(); } @@ -1064,7 +1070,7 @@ public class WebView extends AbsoluteLayout */ public void goBackOrForward(int steps) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "goBackOrForwad=" + steps); + if (TRACE) Log.d(LOGTAG, "goBackOrForwad=" + steps); mProvider.goBackOrForward(steps); } @@ -1084,7 +1090,7 @@ public class WebView extends AbsoluteLayout */ public boolean pageUp(boolean top) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "pageUp"); + if (TRACE) Log.d(LOGTAG, "pageUp"); return mProvider.pageUp(top); } @@ -1096,7 +1102,7 @@ public class WebView extends AbsoluteLayout */ public boolean pageDown(boolean bottom) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "pageDown"); + if (TRACE) Log.d(LOGTAG, "pageDown"); return mProvider.pageDown(bottom); } @@ -1109,7 +1115,7 @@ public class WebView extends AbsoluteLayout @Deprecated public void clearView() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearView"); + if (TRACE) Log.d(LOGTAG, "clearView"); mProvider.clearView(); } @@ -1140,7 +1146,7 @@ public class WebView extends AbsoluteLayout @Deprecated public Picture capturePicture() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "capturePicture"); + if (TRACE) Log.d(LOGTAG, "capturePicture"); return mProvider.capturePicture(); } @@ -1151,7 +1157,7 @@ public class WebView extends AbsoluteLayout @Deprecated public PrintDocumentAdapter createPrintDocumentAdapter() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter"); + if (TRACE) Log.d(LOGTAG, "createPrintDocumentAdapter"); return mProvider.createPrintDocumentAdapter("default"); } @@ -1170,7 +1176,7 @@ public class WebView extends AbsoluteLayout */ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter"); + if (TRACE) Log.d(LOGTAG, "createPrintDocumentAdapter"); return mProvider.createPrintDocumentAdapter(documentName); } @@ -1210,7 +1216,7 @@ public class WebView extends AbsoluteLayout */ public void setInitialScale(int scaleInPercent) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setInitialScale=" + scaleInPercent); + if (TRACE) Log.d(LOGTAG, "setInitialScale=" + scaleInPercent); mProvider.setInitialScale(scaleInPercent); } @@ -1221,7 +1227,7 @@ public class WebView extends AbsoluteLayout */ public void invokeZoomPicker() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "invokeZoomPicker"); + if (TRACE) Log.d(LOGTAG, "invokeZoomPicker"); mProvider.invokeZoomPicker(); } @@ -1245,7 +1251,7 @@ public class WebView extends AbsoluteLayout */ public HitTestResult getHitTestResult() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "getHitTestResult"); + if (TRACE) Log.d(LOGTAG, "getHitTestResult"); return mProvider.getHitTestResult(); } @@ -1264,7 +1270,7 @@ public class WebView extends AbsoluteLayout */ public void requestFocusNodeHref(Message hrefMsg) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "requestFocusNodeHref"); + if (TRACE) Log.d(LOGTAG, "requestFocusNodeHref"); mProvider.requestFocusNodeHref(hrefMsg); } @@ -1277,7 +1283,7 @@ public class WebView extends AbsoluteLayout */ public void requestImageRef(Message msg) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "requestImageRef"); + if (TRACE) Log.d(LOGTAG, "requestImageRef"); mProvider.requestImageRef(msg); } @@ -1382,7 +1388,7 @@ public class WebView extends AbsoluteLayout */ public void pauseTimers() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "pauseTimers"); + if (TRACE) Log.d(LOGTAG, "pauseTimers"); mProvider.pauseTimers(); } @@ -1392,7 +1398,7 @@ public class WebView extends AbsoluteLayout */ public void resumeTimers() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "resumeTimers"); + if (TRACE) Log.d(LOGTAG, "resumeTimers"); mProvider.resumeTimers(); } @@ -1405,7 +1411,7 @@ public class WebView extends AbsoluteLayout */ public void onPause() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "onPause"); + if (TRACE) Log.d(LOGTAG, "onPause"); mProvider.onPause(); } @@ -1414,7 +1420,7 @@ public class WebView extends AbsoluteLayout */ public void onResume() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "onResume"); + if (TRACE) Log.d(LOGTAG, "onResume"); mProvider.onResume(); } @@ -1437,7 +1443,7 @@ public class WebView extends AbsoluteLayout @Deprecated public void freeMemory() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "freeMemory"); + if (TRACE) Log.d(LOGTAG, "freeMemory"); mProvider.freeMemory(); } @@ -1449,7 +1455,7 @@ public class WebView extends AbsoluteLayout */ public void clearCache(boolean includeDiskFiles) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearCache"); + if (TRACE) Log.d(LOGTAG, "clearCache"); mProvider.clearCache(includeDiskFiles); } @@ -1461,7 +1467,7 @@ public class WebView extends AbsoluteLayout */ public void clearFormData() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearFormData"); + if (TRACE) Log.d(LOGTAG, "clearFormData"); mProvider.clearFormData(); } @@ -1470,7 +1476,7 @@ public class WebView extends AbsoluteLayout */ public void clearHistory() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearHistory"); + if (TRACE) Log.d(LOGTAG, "clearHistory"); mProvider.clearHistory(); } @@ -1480,7 +1486,7 @@ public class WebView extends AbsoluteLayout */ public void clearSslPreferences() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearSslPreferences"); + if (TRACE) Log.d(LOGTAG, "clearSslPreferences"); mProvider.clearSslPreferences(); } @@ -1496,7 +1502,7 @@ public class WebView extends AbsoluteLayout * callback. The runnable will be called in UI thread. */ public static void clearClientCertPreferences(Runnable onCleared) { - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearClientCertPreferences"); + if (TRACE) Log.d(LOGTAG, "clearClientCertPreferences"); getFactory().getStatics().clearClientCertPreferences(onCleared); } @@ -1538,7 +1544,7 @@ public class WebView extends AbsoluteLayout */ public void findNext(boolean forward) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "findNext"); + if (TRACE) Log.d(LOGTAG, "findNext"); mProvider.findNext(forward); } @@ -1554,7 +1560,7 @@ public class WebView extends AbsoluteLayout @Deprecated public int findAll(String find) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "findAll"); + if (TRACE) Log.d(LOGTAG, "findAll"); StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync"); return mProvider.findAll(find); } @@ -1569,7 +1575,7 @@ public class WebView extends AbsoluteLayout */ public void findAllAsync(String find) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "findAllAsync"); + if (TRACE) Log.d(LOGTAG, "findAllAsync"); mProvider.findAllAsync(find); } @@ -1590,7 +1596,7 @@ public class WebView extends AbsoluteLayout @Deprecated public boolean showFindDialog(String text, boolean showIme) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "showFindDialog"); + if (TRACE) Log.d(LOGTAG, "showFindDialog"); return mProvider.showFindDialog(text, showIme); } @@ -1646,7 +1652,7 @@ public class WebView extends AbsoluteLayout */ public void clearMatches() { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearMatches"); + if (TRACE) Log.d(LOGTAG, "clearMatches"); mProvider.clearMatches(); } @@ -1707,7 +1713,7 @@ public class WebView extends AbsoluteLayout @Deprecated public void setPictureListener(PictureListener listener) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setPictureListener=" + listener); + if (TRACE) Log.d(LOGTAG, "setPictureListener=" + listener); mProvider.setPictureListener(listener); } @@ -1764,7 +1770,7 @@ public class WebView extends AbsoluteLayout */ public void addJavascriptInterface(Object object, String name) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "addJavascriptInterface=" + name); + if (TRACE) Log.d(LOGTAG, "addJavascriptInterface=" + name); mProvider.addJavascriptInterface(object, name); } @@ -1777,7 +1783,7 @@ public class WebView extends AbsoluteLayout */ public void removeJavascriptInterface(String name) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "removeJavascriptInterface=" + name); + if (TRACE) Log.d(LOGTAG, "removeJavascriptInterface=" + name); mProvider.removeJavascriptInterface(name); } @@ -1881,7 +1887,7 @@ public class WebView extends AbsoluteLayout public void flingScroll(int vx, int vy) { checkThread(); - if (DebugFlags.TRACE_API) Log.d(LOGTAG, "flingScroll"); + if (TRACE) Log.d(LOGTAG, "flingScroll"); mProvider.flingScroll(vx, vy); } @@ -2006,6 +2012,7 @@ public class WebView extends AbsoluteLayout * * @hide WebViewProvider is not public API. */ + @SystemApi public WebViewProvider getWebViewProvider() { return mProvider; } @@ -2015,6 +2022,7 @@ public class WebView extends AbsoluteLayout * and fields, and make super-class calls in this WebView instance. * @hide Only for use by WebViewProvider implementations */ + @SystemApi public class PrivateAccess { // ---- Access to super-class methods ---- public int super_getScrollBarStyle() { diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 99e0ffb..bfea481 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.content.Context; /** @@ -28,18 +29,12 @@ import android.content.Context; * <li>Data entered into text fields (e.g. for autocomplete suggestions)</li> * </ul> */ -public class WebViewDatabase { +public abstract class WebViewDatabase { /** * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ protected static final String LOGTAG = "webviewdatabase"; - /** - * @hide Only for use by WebViewProvider implementations. - */ - protected WebViewDatabase() { - } - public static WebViewDatabase getInstance(Context context) { return WebViewFactory.getProvider().getWebViewDatabase(context); } @@ -54,9 +49,7 @@ public class WebViewDatabase { * @deprecated Saving passwords in WebView will not be supported in future versions. */ @Deprecated - public boolean hasUsernamePassword() { - throw new MustOverrideException(); - } + public abstract boolean hasUsernamePassword(); /** * Clears any saved username/password pairs for web forms. @@ -67,9 +60,7 @@ public class WebViewDatabase { * @deprecated Saving passwords in WebView will not be supported in future versions. */ @Deprecated - public void clearUsernamePassword() { - throw new MustOverrideException(); - } + public abstract void clearUsernamePassword(); /** * Gets whether there are any saved credentials for HTTP authentication. @@ -79,9 +70,7 @@ public class WebViewDatabase { * @see WebView#setHttpAuthUsernamePassword * @see #clearHttpAuthUsernamePassword */ - public boolean hasHttpAuthUsernamePassword() { - throw new MustOverrideException(); - } + public abstract boolean hasHttpAuthUsernamePassword(); /** * Clears any saved credentials for HTTP authentication. @@ -90,9 +79,7 @@ public class WebViewDatabase { * @see WebView#setHttpAuthUsernamePassword * @see #hasHttpAuthUsernamePassword */ - public void clearHttpAuthUsernamePassword() { - throw new MustOverrideException(); - } + public abstract void clearHttpAuthUsernamePassword(); /** * Gets whether there is any saved data for web forms. @@ -100,16 +87,12 @@ public class WebViewDatabase { * @return whether there is any saved data for web forms * @see #clearFormData */ - public boolean hasFormData() { - throw new MustOverrideException(); - } + public abstract boolean hasFormData(); /** * Clears any saved data for web forms. * * @see #hasFormData */ - public void clearFormData() { - throw new MustOverrideException(); - } + public abstract void clearFormData(); } diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java index c878b3d..e03445e 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.app.ActivityThread; import android.app.Application; import android.content.Context; @@ -35,6 +36,7 @@ import android.view.ViewRootImpl; * * @hide */ +@SystemApi public final class WebViewDelegate { /* package */ WebViewDelegate() { } @@ -105,7 +107,7 @@ public final class WebViewDelegate { throw new IllegalArgumentException(canvas.getClass().getName() + " is not hardware accelerated"); } - ((HardwareCanvas) canvas).callDrawGLFunction(nativeDrawGLFunctor); + ((HardwareCanvas) canvas).callDrawGLFunction2(nativeDrawGLFunctor); } /** diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index f185a86..cafe053 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -16,9 +16,10 @@ package android.webkit; +import android.annotation.SystemApi; import android.app.ActivityManagerInternal; -import android.app.Application; import android.app.AppGlobals; +import android.app.Application; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -33,19 +34,20 @@ import android.os.Trace; import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.Log; + import com.android.server.LocalServices; + import dalvik.system.VMRuntime; import java.io.File; import java.util.Arrays; -import com.android.internal.os.Zygote; - /** * Top level factory, used creating all the main WebView implementation classes. * * @hide */ +@SystemApi public final class WebViewFactory { private static final String CHROMIUM_WEBVIEW_FACTORY = @@ -89,6 +91,12 @@ public final class WebViewFactory { // us honest and minimize usage of WebView internals when binding the proxy. if (sProviderInstance != null) return sProviderInstance; + final int uid = android.os.Process.myUid(); + if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) { + throw new UnsupportedOperationException( + "For security reasons, WebView is not allowed in privileged processes"); + } + Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()"); diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index d37d217..9105394 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -26,6 +27,7 @@ import android.net.Uri; * implementation of this interface, and make it available to the WebView via mechanism TBD. * @hide */ +@SystemApi public interface WebViewFactoryProvider { /** * This Interface provides glue for implementing the backend of WebView static methods which diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index fe18138..2aee57b 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.SystemApi; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -53,6 +54,7 @@ import java.util.Map; * * @hide Not part of the public API; only required by system implementors. */ +@SystemApi public interface WebViewProvider { //------------------------------------------------------------------------- // Main interface for backend provider of the WebView class. diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index d39960f..4800c7f 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -16,7 +16,6 @@ package android.widget; -import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; @@ -65,9 +64,6 @@ public abstract class AbsSeekBar extends ProgressBar { * progress. */ private int mKeyProgressIncrement = 1; - private ObjectAnimator mPositionAnimator; - private static final int PROGRESS_ANIMATION_DURATION = 250; - private static final int NO_ALPHA = 0xFF; private float mDisabledAlpha; @@ -388,14 +384,15 @@ public abstract class AbsSeekBar extends ProgressBar { void onProgressRefresh(float scale, boolean fromUser) { super.onProgressRefresh(scale, fromUser); - if (!isAnimationRunning()) { - setThumbPos(scale); - } - } + final Drawable thumb = mThumb; + if (thumb != null) { + setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE); - @Override - void onAnimatePosition(float scale, boolean fromUser) { - setThumbPos(scale); + // Since we draw translated, the drawable's bounds that it signals + // for invalidation won't be the actual bounds we want invalidated, + // so just invalidate this whole view. + invalidate(); + } } @Override @@ -440,18 +437,6 @@ public abstract class AbsSeekBar extends ProgressBar { return max > 0 ? getProgress() / (float) max : 0; } - private void setThumbPos(float scale) { - final Drawable thumb = mThumb; - if (thumb != null) { - setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE); - // Since we draw translated, the drawable's bounds that it signals - // for invalidation won't be the actual bounds we want invalidated, - // so just invalidate this whole view. - invalidate(); - - } - } - /** * Updates the thumb drawable bounds. * @@ -714,13 +699,13 @@ public abstract class AbsSeekBar extends ProgressBar { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: if (progress <= 0) break; - animateSetProgress(progress - mKeyProgressIncrement); + setProgress(progress - mKeyProgressIncrement, true); onKeyChange(); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (progress >= getMax()) break; - animateSetProgress(progress + mKeyProgressIncrement); + setProgress(progress + mKeyProgressIncrement, true); onKeyChange(); return true; } @@ -729,38 +714,6 @@ public abstract class AbsSeekBar extends ProgressBar { return super.onKeyDown(keyCode, event); } - boolean isAnimationRunning() { - return mPositionAnimator != null && mPositionAnimator.isRunning(); - } - - /** - * @hide - */ - @Override - public void setProgress(int progress, boolean fromUser) { - if (isAnimationRunning()) { - mPositionAnimator.cancel(); - } - super.setProgress(progress, fromUser); - } - - void animateSetProgress(int progress) { - float curProgress = isAnimationRunning() ? getAnimationPosition() : getProgress(); - - if (progress < 0) { - progress = 0; - } else if (progress > getMax()) { - progress = getMax(); - } - setProgressValueOnly(progress); - - mPositionAnimator = ObjectAnimator.ofFloat(this, "animationPosition", curProgress, - progress); - mPositionAnimator.setDuration(PROGRESS_ANIMATION_DURATION); - mPositionAnimator.setAutoCancel(true); - mPositionAnimator.start(); - } - @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index d779124..18f15a0 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -655,15 +655,19 @@ public class ActionMenuPresenter extends BaseMenuPresenter protected boolean setFrame(int l, int t, int r, int b) { final boolean changed = super.setFrame(l, t, r, b); - // Set up the hotspot bounds to be centered on the image. + // Set up the hotspot bounds to square and centered on the image. final Drawable d = getDrawable(); final Drawable bg = getBackground(); if (d != null && bg != null) { - final float[] pts = mTempPts; - pts[0] = d.getBounds().centerX(); - getImageMatrix().mapPoints(pts); - final int offset = (int) pts[0] - getWidth() / 2; - bg.setHotspotBounds(offset, 0, getWidth() + offset, getHeight()); + final int width = getWidth(); + final int height = getHeight(); + final int halfEdge = Math.max(width, height) / 2; + final int offsetX = getPaddingLeft() - getPaddingRight(); + final int offsetY = getPaddingTop() - getPaddingBottom(); + final int centerX = (width + offsetX) / 2; + final int centerY = (height + offsetY) / 2; + bg.setHotspotBounds(centerX - halfEdge, centerY - halfEdge, + centerX + halfEdge, centerY + halfEdge); } return changed; diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 3b16aba..e6392b9 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -1005,6 +1005,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); + + if (mTemporaryDetach) { + // If we are temporarily in the detach state, then do nothing. + return; + } + // Perform validation if the view is losing focus. if (!focused) { performValidation(); diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index f380d68..ed59ea6 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -17,42 +17,24 @@ package android.widget; import android.annotation.Widget; -import android.app.Service; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Paint.Style; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.text.format.DateUtils; import android.util.AttributeSet; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.AbsListView.OnScrollListener; import com.android.internal.R; +import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; -import libcore.icu.LocaleData; - /** * This class is a calendar widget for displaying and selecting dates. The range * of dates supported by this calendar is configurable. A user can select a date @@ -74,13 +56,12 @@ import libcore.icu.LocaleData; */ @Widget public class CalendarView extends FrameLayout { + private static final String LOG_TAG = "CalendarView"; - /** - * Tag for logging. - */ - private static final String LOG_TAG = CalendarView.class.getSimpleName(); + private static final int MODE_HOLO = 0; + private static final int MODE_MATERIAL = 1; - private CalendarViewDelegate mDelegate; + private final CalendarViewDelegate mDelegate; /** * The callback used to indicate the user changes the date. @@ -113,7 +94,23 @@ public class CalendarView extends FrameLayout { public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.CalendarView, defStyleAttr, defStyleRes); + final int mode = a.getInt(R.styleable.CalendarView_calendarViewMode, MODE_HOLO); + a.recycle(); + + switch (mode) { + case MODE_HOLO: + mDelegate = new CalendarViewLegacyDelegate( + this, context, attrs, defStyleAttr, defStyleRes); + break; + case MODE_MATERIAL: + mDelegate = new CalendarViewMaterialDelegate( + this, context, attrs, defStyleAttr, defStyleRes); + break; + default: + throw new IllegalArgumentException("invalid calendarViewMode attribute"); + } } /** @@ -326,16 +323,6 @@ public class CalendarView extends FrameLayout { return mDelegate.getDateTextAppearance(); } - @Override - public void setEnabled(boolean enabled) { - mDelegate.setEnabled(enabled); - } - - @Override - public boolean isEnabled() { - return mDelegate.isEnabled(); - } - /** * Gets the minimal date supported by this {@link CalendarView} in milliseconds * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time @@ -516,14 +503,12 @@ public class CalendarView extends FrameLayout { @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - mDelegate.onInitializeAccessibilityEvent(event); + event.setClassName(CalendarView.class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - mDelegate.onInitializeAccessibilityNodeInfo(info); + info.setClassName(CalendarView.class.getName()); } /** @@ -560,9 +545,6 @@ public class CalendarView extends FrameLayout { void setDateTextAppearance(int resourceId); int getDateTextAppearance(); - void setEnabled(boolean enabled); - boolean isEnabled(); - void setMinDate(long minDate); long getMinDate(); @@ -582,21 +564,26 @@ public class CalendarView extends FrameLayout { void setOnDateChangeListener(OnDateChangeListener listener); void onConfigurationChanged(Configuration newConfig); - void onInitializeAccessibilityEvent(AccessibilityEvent event); - void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); } /** * An abstract class which can be used as a start for CalendarView implementations */ abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate { - // The delegator - protected CalendarView mDelegator; + /** String for parsing dates. */ + private static final String DATE_FORMAT = "MM/dd/yyyy"; - // The context - protected Context mContext; + /** The default minimal date. */ + protected static final String DEFAULT_MIN_DATE = "01/01/1900"; + + /** The default maximal date. */ + protected static final String DEFAULT_MAX_DATE = "01/01/2100"; - // The current locale + /** Date format for parsing dates. */ + protected static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + + protected CalendarView mDelegator; + protected Context mContext; protected Locale mCurrentLocale; AbstractCalendarViewDelegate(CalendarView delegator, Context context) { @@ -613,830 +600,6 @@ public class CalendarView extends FrameLayout { } mCurrentLocale = locale; } - } - - /** - * A delegate implementing the legacy CalendarView - */ - private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate { - - /** - * Default value whether to show week number. - */ - private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; - - /** - * The number of milliseconds in a day.e - */ - private static final long MILLIS_IN_DAY = 86400000L; - - /** - * The number of day in a week. - */ - private static final int DAYS_PER_WEEK = 7; - - /** - * The number of milliseconds in a week. - */ - private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; - - /** - * Affects when the month selection will change while scrolling upe - */ - private static final int SCROLL_HYST_WEEKS = 2; - - /** - * How long the GoTo fling animation should last. - */ - private static final int GOTO_SCROLL_DURATION = 1000; - - /** - * The duration of the adjustment upon a user scroll in milliseconds. - */ - private static final int ADJUSTMENT_SCROLL_DURATION = 500; - - /** - * How long to wait after receiving an onScrollStateChanged notification - * before acting on it. - */ - private static final int SCROLL_CHANGE_DELAY = 40; - - /** - * String for parsing dates. - */ - private static final String DATE_FORMAT = "MM/dd/yyyy"; - - /** - * The default minimal date. - */ - private static final String DEFAULT_MIN_DATE = "01/01/1900"; - - /** - * The default maximal date. - */ - private static final String DEFAULT_MAX_DATE = "01/01/2100"; - - private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; - - private static final int DEFAULT_DATE_TEXT_SIZE = 14; - - private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; - - private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; - - private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; - - private static final int UNSCALED_BOTTOM_BUFFER = 20; - - private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; - - private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; - - private final int mWeekSeperatorLineWidth; - - private int mDateTextSize; - - private Drawable mSelectedDateVerticalBar; - - private final int mSelectedDateVerticalBarWidth; - - private int mSelectedWeekBackgroundColor; - - private int mFocusedMonthDateColor; - - private int mUnfocusedMonthDateColor; - - private int mWeekSeparatorLineColor; - - private int mWeekNumberColor; - - private int mWeekDayTextAppearanceResId; - - private int mDateTextAppearanceResId; - - /** - * The top offset of the weeks list. - */ - private int mListScrollTopOffset = 2; - - /** - * The visible height of a week view. - */ - private int mWeekMinVisibleHeight = 12; - - /** - * The visible height of a week view. - */ - private int mBottomBuffer = 20; - - /** - * The number of shown weeks. - */ - private int mShownWeekCount; - - /** - * Flag whether to show the week number. - */ - private boolean mShowWeekNumber; - - /** - * The number of day per week to be shown. - */ - private int mDaysPerWeek = 7; - - /** - * The friction of the week list while flinging. - */ - private float mFriction = .05f; - - /** - * Scale for adjusting velocity of the week list while flinging. - */ - private float mVelocityScale = 0.333f; - - /** - * The adapter for the weeks list. - */ - private WeeksAdapter mAdapter; - - /** - * The weeks list. - */ - private ListView mListView; - - /** - * The name of the month to display. - */ - private TextView mMonthName; - - /** - * The header with week day names. - */ - private ViewGroup mDayNamesHeader; - - /** - * Cached abbreviations for day of week names. - */ - private String[] mDayNamesShort; - - /** - * Cached full-length day of week names. - */ - private String[] mDayNamesLong; - - /** - * The first day of the week. - */ - private int mFirstDayOfWeek; - - /** - * Which month should be displayed/highlighted [0-11]. - */ - private int mCurrentMonthDisplayed = -1; - - /** - * Used for tracking during a scroll. - */ - private long mPreviousScrollPosition; - - /** - * Used for tracking which direction the view is scrolling. - */ - private boolean mIsScrollingUp = false; - - /** - * The previous scroll state of the weeks ListView. - */ - private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * The current scroll state of the weeks ListView. - */ - private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * Listener for changes in the selected day. - */ - private OnDateChangeListener mOnDateChangeListener; - - /** - * Command for adjusting the position after a scroll/fling. - */ - private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); - - /** - * Temporary instance to avoid multiple instantiations. - */ - private Calendar mTempDate; - - /** - * The first day of the focused month. - */ - private Calendar mFirstDayOfMonth; - - /** - * The start date of the range supported by this picker. - */ - private Calendar mMinDate; - - /** - * The end date of the range supported by this picker. - */ - private Calendar mMaxDate; - - /** - * Date format for parsing dates. - */ - private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); - - LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(delegator, context); - - // initialization based on locale - setCurrentLocale(Locale.getDefault()); - - TypedArray attributesArray = context.obtainStyledAttributes(attrs, - R.styleable.CalendarView, defStyleAttr, defStyleRes); - mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, - DEFAULT_SHOW_WEEK_NUMBER); - mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, - LocaleData.get(Locale.getDefault()).firstDayOfWeek); - String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); - if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { - parseDate(DEFAULT_MIN_DATE, mMinDate); - } - String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); - if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { - parseDate(DEFAULT_MAX_DATE, mMaxDate); - } - if (mMaxDate.before(mMinDate)) { - throw new IllegalArgumentException("Max date cannot be before min date."); - } - mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, - DEFAULT_SHOWN_WEEK_COUNT); - mSelectedWeekBackgroundColor = attributesArray.getColor( - R.styleable.CalendarView_selectedWeekBackgroundColor, 0); - mFocusedMonthDateColor = attributesArray.getColor( - R.styleable.CalendarView_focusedMonthDateColor, 0); - mUnfocusedMonthDateColor = attributesArray.getColor( - R.styleable.CalendarView_unfocusedMonthDateColor, 0); - mWeekSeparatorLineColor = attributesArray.getColor( - R.styleable.CalendarView_weekSeparatorLineColor, 0); - mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); - mSelectedDateVerticalBar = attributesArray.getDrawable( - R.styleable.CalendarView_selectedDateVerticalBar); - - mDateTextAppearanceResId = attributesArray.getResourceId( - R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); - updateDateTextSize(); - - mWeekDayTextAppearanceResId = attributesArray.getResourceId( - R.styleable.CalendarView_weekDayTextAppearance, - DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); - attributesArray.recycle(); - - DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics(); - mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); - mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); - mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_BOTTOM_BUFFER, displayMetrics); - mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); - mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); - - LayoutInflater layoutInflater = (LayoutInflater) mContext - .getSystemService(Service.LAYOUT_INFLATER_SERVICE); - View content = layoutInflater.inflate(R.layout.calendar_view, null, false); - mDelegator.addView(content); - - mListView = (ListView) mDelegator.findViewById(R.id.list); - mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); - mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); - - setUpHeader(); - setUpListView(); - setUpAdapter(); - - // go to today or whichever is close to today min or max date - mTempDate.setTimeInMillis(System.currentTimeMillis()); - if (mTempDate.before(mMinDate)) { - goTo(mMinDate, false, true, true); - } else if (mMaxDate.before(mTempDate)) { - goTo(mMaxDate, false, true, true); - } else { - goTo(mTempDate, false, true, true); - } - - mDelegator.invalidate(); - } - - @Override - public void setShownWeekCount(int count) { - if (mShownWeekCount != count) { - mShownWeekCount = count; - mDelegator.invalidate(); - } - } - - @Override - public int getShownWeekCount() { - return mShownWeekCount; - } - - @Override - public void setSelectedWeekBackgroundColor(int color) { - if (mSelectedWeekBackgroundColor != color) { - mSelectedWeekBackgroundColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasSelectedDay) { - weekView.invalidate(); - } - } - } - } - - @Override - public int getSelectedWeekBackgroundColor() { - return mSelectedWeekBackgroundColor; - } - - @Override - public void setFocusedMonthDateColor(int color) { - if (mFocusedMonthDateColor != color) { - mFocusedMonthDateColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasFocusedDay) { - weekView.invalidate(); - } - } - } - } - - @Override - public int getFocusedMonthDateColor() { - return mFocusedMonthDateColor; - } - - @Override - public void setUnfocusedMonthDateColor(int color) { - if (mUnfocusedMonthDateColor != color) { - mUnfocusedMonthDateColor = color; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasUnfocusedDay) { - weekView.invalidate(); - } - } - } - } - - @Override - public int getUnfocusedMonthDateColor() { - return mFocusedMonthDateColor; - } - - @Override - public void setWeekNumberColor(int color) { - if (mWeekNumberColor != color) { - mWeekNumberColor = color; - if (mShowWeekNumber) { - invalidateAllWeekViews(); - } - } - } - - @Override - public int getWeekNumberColor() { - return mWeekNumberColor; - } - - @Override - public void setWeekSeparatorLineColor(int color) { - if (mWeekSeparatorLineColor != color) { - mWeekSeparatorLineColor = color; - invalidateAllWeekViews(); - } - } - - @Override - public int getWeekSeparatorLineColor() { - return mWeekSeparatorLineColor; - } - - @Override - public void setSelectedDateVerticalBar(int resourceId) { - Drawable drawable = mDelegator.getContext().getDrawable(resourceId); - setSelectedDateVerticalBar(drawable); - } - - @Override - public void setSelectedDateVerticalBar(Drawable drawable) { - if (mSelectedDateVerticalBar != drawable) { - mSelectedDateVerticalBar = drawable; - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - WeekView weekView = (WeekView) mListView.getChildAt(i); - if (weekView.mHasSelectedDay) { - weekView.invalidate(); - } - } - } - } - - @Override - public Drawable getSelectedDateVerticalBar() { - return mSelectedDateVerticalBar; - } - - @Override - public void setWeekDayTextAppearance(int resourceId) { - if (mWeekDayTextAppearanceResId != resourceId) { - mWeekDayTextAppearanceResId = resourceId; - setUpHeader(); - } - } - - @Override - public int getWeekDayTextAppearance() { - return mWeekDayTextAppearanceResId; - } - - @Override - public void setDateTextAppearance(int resourceId) { - if (mDateTextAppearanceResId != resourceId) { - mDateTextAppearanceResId = resourceId; - updateDateTextSize(); - invalidateAllWeekViews(); - } - } - - @Override - public int getDateTextAppearance() { - return mDateTextAppearanceResId; - } - - @Override - public void setEnabled(boolean enabled) { - mListView.setEnabled(enabled); - } - - @Override - public boolean isEnabled() { - return mListView.isEnabled(); - } - - @Override - public void setMinDate(long minDate) { - mTempDate.setTimeInMillis(minDate); - if (isSameDate(mTempDate, mMinDate)) { - return; - } - mMinDate.setTimeInMillis(minDate); - // make sure the current date is not earlier than - // the new min date since the latter is used for - // calculating the indices in the adapter thus - // avoiding out of bounds error - Calendar date = mAdapter.mSelectedDate; - if (date.before(mMinDate)) { - mAdapter.setSelectedDay(mMinDate); - } - // reinitialize the adapter since its range depends on min date - mAdapter.init(); - if (date.before(mMinDate)) { - setDate(mTempDate.getTimeInMillis()); - } else { - // we go to the current date to force the ListView to query its - // adapter for the shown views since we have changed the adapter - // range and the base from which the later calculates item indices - // note that calling setDate will not work since the date is the same - goTo(date, false, true, false); - } - } - - @Override - public long getMinDate() { - return mMinDate.getTimeInMillis(); - } - - @Override - public void setMaxDate(long maxDate) { - mTempDate.setTimeInMillis(maxDate); - if (isSameDate(mTempDate, mMaxDate)) { - return; - } - mMaxDate.setTimeInMillis(maxDate); - // reinitialize the adapter since its range depends on max date - mAdapter.init(); - Calendar date = mAdapter.mSelectedDate; - if (date.after(mMaxDate)) { - setDate(mMaxDate.getTimeInMillis()); - } else { - // we go to the current date to force the ListView to query its - // adapter for the shown views since we have changed the adapter - // range and the base from which the later calculates item indices - // note that calling setDate will not work since the date is the same - goTo(date, false, true, false); - } - } - - @Override - public long getMaxDate() { - return mMaxDate.getTimeInMillis(); - } - - @Override - public void setShowWeekNumber(boolean showWeekNumber) { - if (mShowWeekNumber == showWeekNumber) { - return; - } - mShowWeekNumber = showWeekNumber; - mAdapter.notifyDataSetChanged(); - setUpHeader(); - } - - @Override - public boolean getShowWeekNumber() { - return mShowWeekNumber; - } - - @Override - public void setFirstDayOfWeek(int firstDayOfWeek) { - if (mFirstDayOfWeek == firstDayOfWeek) { - return; - } - mFirstDayOfWeek = firstDayOfWeek; - mAdapter.init(); - mAdapter.notifyDataSetChanged(); - setUpHeader(); - } - - @Override - public int getFirstDayOfWeek() { - return mFirstDayOfWeek; - } - - @Override - public void setDate(long date) { - setDate(date, false, false); - } - - @Override - public void setDate(long date, boolean animate, boolean center) { - mTempDate.setTimeInMillis(date); - if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { - return; - } - goTo(mTempDate, animate, true, center); - } - - @Override - public long getDate() { - return mAdapter.mSelectedDate.getTimeInMillis(); - } - - @Override - public void setOnDateChangeListener(OnDateChangeListener listener) { - mOnDateChangeListener = listener; - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - setCurrentLocale(newConfig.locale); - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - event.setClassName(CalendarView.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - info.setClassName(CalendarView.class.getName()); - } - - /** - * Sets the current locale. - * - * @param locale The current locale. - */ - @Override - protected void setCurrentLocale(Locale locale) { - super.setCurrentLocale(locale); - - mTempDate = getCalendarForLocale(mTempDate, locale); - mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); - mMinDate = getCalendarForLocale(mMinDate, locale); - mMaxDate = getCalendarForLocale(mMaxDate, locale); - } - private void updateDateTextSize() { - TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes( - mDateTextAppearanceResId, R.styleable.TextAppearance); - mDateTextSize = dateTextAppearance.getDimensionPixelSize( - R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); - dateTextAppearance.recycle(); - } - - /** - * Invalidates all week views. - */ - private void invalidateAllWeekViews() { - final int childCount = mListView.getChildCount(); - for (int i = 0; i < childCount; i++) { - View view = mListView.getChildAt(i); - view.invalidate(); - } - } - - /** - * Gets a calendar for locale bootstrapped with the value of a given calendar. - * - * @param oldCalendar The old calendar. - * @param locale The locale. - */ - private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { - if (oldCalendar == null) { - return Calendar.getInstance(locale); - } else { - final long currentTimeMillis = oldCalendar.getTimeInMillis(); - Calendar newCalendar = Calendar.getInstance(locale); - newCalendar.setTimeInMillis(currentTimeMillis); - return newCalendar; - } - } - - /** - * @return True if the <code>firstDate</code> is the same as the <code> - * secondDate</code>. - */ - private static boolean isSameDate(Calendar firstDate, Calendar secondDate) { - return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) - && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); - } - - /** - * Creates a new adapter if necessary and sets up its parameters. - */ - private void setUpAdapter() { - if (mAdapter == null) { - mAdapter = new WeeksAdapter(mContext); - mAdapter.registerDataSetObserver(new DataSetObserver() { - @Override - public void onChanged() { - if (mOnDateChangeListener != null) { - Calendar selectedDay = mAdapter.getSelectedDay(); - mOnDateChangeListener.onSelectedDayChange(mDelegator, - selectedDay.get(Calendar.YEAR), - selectedDay.get(Calendar.MONTH), - selectedDay.get(Calendar.DAY_OF_MONTH)); - } - } - }); - mListView.setAdapter(mAdapter); - } - - // refresh the view with the new parameters - mAdapter.notifyDataSetChanged(); - } - - /** - * Sets up the strings to be used by the header. - */ - private void setUpHeader() { - mDayNamesShort = new String[mDaysPerWeek]; - mDayNamesLong = new String[mDaysPerWeek]; - for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { - int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; - mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, - DateUtils.LENGTH_SHORTEST); - mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, - DateUtils.LENGTH_LONG); - } - - TextView label = (TextView) mDayNamesHeader.getChildAt(0); - if (mShowWeekNumber) { - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); - } - for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { - label = (TextView) mDayNamesHeader.getChildAt(i); - if (mWeekDayTextAppearanceResId > -1) { - label.setTextAppearance(mContext, mWeekDayTextAppearanceResId); - } - if (i < mDaysPerWeek + 1) { - label.setText(mDayNamesShort[i - 1]); - label.setContentDescription(mDayNamesLong[i - 1]); - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); - } - } - mDayNamesHeader.invalidate(); - } - - /** - * Sets all the required fields for the list view. - */ - private void setUpListView() { - // Configure the listview - mListView.setDivider(null); - mListView.setItemsCanFocus(true); - mListView.setVerticalScrollBarEnabled(false); - mListView.setOnScrollListener(new OnScrollListener() { - public void onScrollStateChanged(AbsListView view, int scrollState) { - LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState); - } - - public void onScroll( - AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem, - visibleItemCount, totalItemCount); - } - }); - // Make the scrolling behavior nicer - mListView.setFriction(mFriction); - mListView.setVelocityScale(mVelocityScale); - } - - /** - * This moves to the specified time in the view. If the time is not already - * in range it will move the list so that the first of the month containing - * the time is at the top of the view. If the new time is already in view - * the list will not be scrolled unless forceScroll is true. This time may - * optionally be highlighted as selected as well. - * - * @param date The time to move to. - * @param animate Whether to scroll to the given time or just redraw at the - * new location. - * @param setSelected Whether to set the given time as selected. - * @param forceScroll Whether to recenter even if the time is already - * visible. - * - * @throws IllegalArgumentException of the provided date is before the - * range start of after the range end. - */ - private void goTo(Calendar date, boolean animate, boolean setSelected, - boolean forceScroll) { - if (date.before(mMinDate) || date.after(mMaxDate)) { - throw new IllegalArgumentException("Time not between " + mMinDate.getTime() - + " and " + mMaxDate.getTime()); - } - // Find the first and last entirely visible weeks - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - View firstChild = mListView.getChildAt(0); - if (firstChild != null && firstChild.getTop() < 0) { - firstFullyVisiblePosition++; - } - int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; - if (firstChild != null && firstChild.getTop() > mBottomBuffer) { - lastFullyVisiblePosition--; - } - if (setSelected) { - mAdapter.setSelectedDay(date); - } - // Get the week we're going to - int position = getWeeksSinceMinDate(date); - - // Check if the selected day is now outside of our visible range - // and if so scroll to the month that contains it - if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition - || forceScroll) { - mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); - mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); - - setMonthDisplayed(mFirstDayOfMonth); - - // the earliest time we can scroll to is the min date - if (mFirstDayOfMonth.before(mMinDate)) { - position = 0; - } else { - position = getWeeksSinceMinDate(mFirstDayOfMonth); - } - - mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; - if (animate) { - mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, - GOTO_SCROLL_DURATION); - } else { - mListView.setSelectionFromTop(position, mListScrollTopOffset); - // Perform any after scroll operations that are needed - onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); - } - } else if (setSelected) { - // Otherwise just set the selection - setMonthDisplayed(date); - } - } /** * Parses the given <code>date</code> and in case of success sets @@ -1444,718 +607,15 @@ public class CalendarView extends FrameLayout { * * @return True if the date was parsed. */ - private boolean parseDate(String date, Calendar outDate) { + protected boolean parseDate(String date, Calendar outDate) { try { - outDate.setTime(mDateFormat.parse(date)); + outDate.setTime(DATE_FORMATTER.parse(date)); return true; } catch (ParseException e) { Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); return false; } } - - /** - * Called when a <code>view</code> transitions to a new <code>scrollState - * </code>. - */ - private void onScrollStateChanged(AbsListView view, int scrollState) { - mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); - } - - /** - * Updates the title and selected month if the <code>view</code> has moved to a new - * month. - */ - private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - WeekView child = (WeekView) view.getChildAt(0); - if (child == null) { - return; - } - - // Figure out where we are - long currScroll = - view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); - - // If we have moved since our last call update the direction - if (currScroll < mPreviousScrollPosition) { - mIsScrollingUp = true; - } else if (currScroll > mPreviousScrollPosition) { - mIsScrollingUp = false; - } else { - return; - } - - // Use some hysteresis for checking which month to highlight. This - // causes the month to transition when two full weeks of a month are - // visible when scrolling up, and when the first day in a month reaches - // the top of the screen when scrolling down. - int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; - if (mIsScrollingUp) { - child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); - } else if (offset != 0) { - child = (WeekView) view.getChildAt(offset); - } - - if (child != null) { - // Find out which month we're moving into - int month; - if (mIsScrollingUp) { - month = child.getMonthOfFirstWeekDay(); - } else { - month = child.getMonthOfLastWeekDay(); - } - - // And how it relates to our current highlighted month - int monthDiff; - if (mCurrentMonthDisplayed == 11 && month == 0) { - monthDiff = 1; - } else if (mCurrentMonthDisplayed == 0 && month == 11) { - monthDiff = -1; - } else { - monthDiff = month - mCurrentMonthDisplayed; - } - - // Only switch months if we're scrolling away from the currently - // selected month - if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { - Calendar firstDay = child.getFirstDay(); - if (mIsScrollingUp) { - firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); - } else { - firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); - } - setMonthDisplayed(firstDay); - } - } - mPreviousScrollPosition = currScroll; - mPreviousScrollState = mCurrentScrollState; - } - - /** - * Sets the month displayed at the top of this view based on time. Override - * to add custom events when the title is changed. - * - * @param calendar A day in the new focus month. - */ - private void setMonthDisplayed(Calendar calendar) { - mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); - mAdapter.setFocusMonth(mCurrentMonthDisplayed); - final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY - | DateUtils.FORMAT_SHOW_YEAR; - final long millis = calendar.getTimeInMillis(); - String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); - mMonthName.setText(newMonthName); - mMonthName.invalidate(); - } - - /** - * @return Returns the number of weeks between the current <code>date</code> - * and the <code>mMinDate</code>. - */ - private int getWeeksSinceMinDate(Calendar date) { - if (date.before(mMinDate)) { - throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() - + " does not precede toDate: " + date.getTime()); - } - long endTimeMillis = date.getTimeInMillis() - + date.getTimeZone().getOffset(date.getTimeInMillis()); - long startTimeMillis = mMinDate.getTimeInMillis() - + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); - long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) - * MILLIS_IN_DAY; - return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); - } - - /** - * Command responsible for acting upon scroll state changes. - */ - private class ScrollStateRunnable implements Runnable { - private AbsListView mView; - - private int mNewState; - - /** - * Sets up the runnable with a short delay in case the scroll state - * immediately changes again. - * - * @param view The list view that changed state - * @param scrollState The new state it changed to - */ - public void doScrollStateChange(AbsListView view, int scrollState) { - mView = view; - mNewState = scrollState; - mDelegator.removeCallbacks(this); - mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY); - } - - public void run() { - mCurrentScrollState = mNewState; - // Fix the position after a scroll or a fling ends - if (mNewState == OnScrollListener.SCROLL_STATE_IDLE - && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { - View child = mView.getChildAt(0); - if (child == null) { - // The view is no longer visible, just return - return; - } - int dist = child.getBottom() - mListScrollTopOffset; - if (dist > mListScrollTopOffset) { - if (mIsScrollingUp) { - mView.smoothScrollBy(dist - child.getHeight(), - ADJUSTMENT_SCROLL_DURATION); - } else { - mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); - } - } - } - mPreviousScrollState = mNewState; - } - } - - /** - * <p> - * This is a specialized adapter for creating a list of weeks with - * selectable days. It can be configured to display the week number, start - * the week on a given day, show a reduced number of days, or display an - * arbitrary number of weeks at a time. - * </p> - */ - private class WeeksAdapter extends BaseAdapter implements OnTouchListener { - - private int mSelectedWeek; - - private GestureDetector mGestureDetector; - - private int mFocusedMonth; - - private final Calendar mSelectedDate = Calendar.getInstance(); - - private int mTotalWeekCount; - - public WeeksAdapter(Context context) { - mContext = context; - mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); - init(); - } - - /** - * Set up the gesture detector and selected time - */ - private void init() { - mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); - mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); - if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek - || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { - mTotalWeekCount++; - } - notifyDataSetChanged(); - } - - /** - * Updates the selected day and related parameters. - * - * @param selectedDay The time to highlight - */ - public void setSelectedDay(Calendar selectedDay) { - if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) - && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { - return; - } - mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); - mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); - mFocusedMonth = mSelectedDate.get(Calendar.MONTH); - notifyDataSetChanged(); - } - - /** - * @return The selected day of month. - */ - public Calendar getSelectedDay() { - return mSelectedDate; - } - - @Override - public int getCount() { - return mTotalWeekCount; - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - WeekView weekView = null; - if (convertView != null) { - weekView = (WeekView) convertView; - } else { - weekView = new WeekView(mContext); - android.widget.AbsListView.LayoutParams params = - new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - weekView.setLayoutParams(params); - weekView.setClickable(true); - weekView.setOnTouchListener(this); - } - - int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( - Calendar.DAY_OF_WEEK) : -1; - weekView.init(position, selectedWeekDay, mFocusedMonth); - - return weekView; - } - - /** - * Changes which month is in focus and updates the view. - * - * @param month The month to show as in focus [0-11] - */ - public void setFocusMonth(int month) { - if (mFocusedMonth == month) { - return; - } - mFocusedMonth = month; - notifyDataSetChanged(); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { - WeekView weekView = (WeekView) v; - // if we cannot find a day for the given location we are done - if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { - return true; - } - // it is possible that the touched day is outside the valid range - // we draw whole weeks but range end can fall not on the week end - if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { - return true; - } - onDateTapped(mTempDate); - return true; - } - return false; - } - - /** - * Maintains the same hour/min/sec but moves the day to the tapped day. - * - * @param day The day that was tapped - */ - private void onDateTapped(Calendar day) { - setSelectedDay(day); - setMonthDisplayed(day); - } - - /** - * This is here so we can identify single tap events and set the - * selected day correctly - */ - class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onSingleTapUp(MotionEvent e) { - return true; - } - } - } - - /** - * <p> - * This is a dynamic view for drawing a single week. It can be configured to - * display the week number, start the week on a given day, or show a reduced - * number of days. It is intended for use as a single view within a - * ListView. See {@link WeeksAdapter} for usage. - * </p> - */ - private class WeekView extends View { - - private final Rect mTempRect = new Rect(); - - private final Paint mDrawPaint = new Paint(); - - private final Paint mMonthNumDrawPaint = new Paint(); - - // Cache the number strings so we don't have to recompute them each time - private String[] mDayNumbers; - - // Quick lookup for checking which days are in the focus month - private boolean[] mFocusDay; - - // Whether this view has a focused day. - private boolean mHasFocusedDay; - - // Whether this view has only focused days. - private boolean mHasUnfocusedDay; - - // The first day displayed by this item - private Calendar mFirstDay; - - // The month of the first day in this week - private int mMonthOfFirstWeekDay = -1; - - // The month of the last day in this week - private int mLastWeekDayMonth = -1; - - // The position of this week, equivalent to weeks since the week of Jan - // 1st, 1900 - private int mWeek = -1; - - // Quick reference to the width of this view, matches parent - private int mWidth; - - // The height this view should draw at in pixels, set by height param - private int mHeight; - - // If this view contains the selected day - private boolean mHasSelectedDay = false; - - // Which day is selected [0-6] or -1 if no day is selected - private int mSelectedDay = -1; - - // The number of days + a spot for week number if it is displayed - private int mNumCells; - - // The left edge of the selected day - private int mSelectedLeft = -1; - - // The right edge of the selected day - private int mSelectedRight = -1; - - public WeekView(Context context) { - super(context); - - // Sets up any standard paints that will be used - initilaizePaints(); - } - - /** - * Initializes this week view. - * - * @param weekNumber The number of the week this view represents. The - * week number is a zero based index of the weeks since - * {@link CalendarView#getMinDate()}. - * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no - * selected day. - * @param focusedMonth The month that is currently in focus i.e. - * highlighted. - */ - public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { - mSelectedDay = selectedWeekDay; - mHasSelectedDay = mSelectedDay != -1; - mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; - mWeek = weekNumber; - mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); - - mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); - mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); - - // Allocate space for caching the day numbers and focus values - mDayNumbers = new String[mNumCells]; - mFocusDay = new boolean[mNumCells]; - - // If we're showing the week number calculate it based on Monday - int i = 0; - if (mShowWeekNumber) { - mDayNumbers[0] = String.format(Locale.getDefault(), "%d", - mTempDate.get(Calendar.WEEK_OF_YEAR)); - i++; - } - - // Now adjust our starting day based on the start day of the week - int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); - mTempDate.add(Calendar.DAY_OF_MONTH, diff); - - mFirstDay = (Calendar) mTempDate.clone(); - mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); - - mHasUnfocusedDay = true; - for (; i < mNumCells; i++) { - final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); - mFocusDay[i] = isFocusedDay; - mHasFocusedDay |= isFocusedDay; - mHasUnfocusedDay &= !isFocusedDay; - // do not draw dates outside the valid range to avoid user confusion - if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { - mDayNumbers[i] = ""; - } else { - mDayNumbers[i] = String.format(Locale.getDefault(), "%d", - mTempDate.get(Calendar.DAY_OF_MONTH)); - } - mTempDate.add(Calendar.DAY_OF_MONTH, 1); - } - // We do one extra add at the end of the loop, if that pushed us to - // new month undo it - if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { - mTempDate.add(Calendar.DAY_OF_MONTH, -1); - } - mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); - - updateSelectionPositions(); - } - - /** - * Initialize the paint instances. - */ - private void initilaizePaints() { - mDrawPaint.setFakeBoldText(false); - mDrawPaint.setAntiAlias(true); - mDrawPaint.setStyle(Style.FILL); - - mMonthNumDrawPaint.setFakeBoldText(true); - mMonthNumDrawPaint.setAntiAlias(true); - mMonthNumDrawPaint.setStyle(Style.FILL); - mMonthNumDrawPaint.setTextAlign(Align.CENTER); - mMonthNumDrawPaint.setTextSize(mDateTextSize); - } - - /** - * Returns the month of the first day in this week. - * - * @return The month the first day of this view is in. - */ - public int getMonthOfFirstWeekDay() { - return mMonthOfFirstWeekDay; - } - - /** - * Returns the month of the last day in this week - * - * @return The month the last day of this view is in - */ - public int getMonthOfLastWeekDay() { - return mLastWeekDayMonth; - } - - /** - * Returns the first day in this view. - * - * @return The first day in the view. - */ - public Calendar getFirstDay() { - return mFirstDay; - } - - /** - * Calculates the day that the given x position is in, accounting for - * week number. - * - * @param x The x position of the touch event. - * @return True if a day was found for the given location. - */ - public boolean getDayFromLocation(float x, Calendar outCalendar) { - final boolean isLayoutRtl = isLayoutRtl(); - - int start; - int end; - - if (isLayoutRtl) { - start = 0; - end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - start = mShowWeekNumber ? mWidth / mNumCells : 0; - end = mWidth; - } - - if (x < start || x > end) { - outCalendar.clear(); - return false; - } - - // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels - int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); - - if (isLayoutRtl) { - dayPosition = mDaysPerWeek - 1 - dayPosition; - } - - outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); - outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); - - return true; - } - - @Override - protected void onDraw(Canvas canvas) { - drawBackground(canvas); - drawWeekNumbersAndDates(canvas); - drawWeekSeparators(canvas); - drawSelectedDateVerticalBars(canvas); - } - - /** - * This draws the selection highlight if a day is selected in this week. - * - * @param canvas The canvas to draw on - */ - private void drawBackground(Canvas canvas) { - if (!mHasSelectedDay) { - return; - } - mDrawPaint.setColor(mSelectedWeekBackgroundColor); - - mTempRect.top = mWeekSeperatorLineWidth; - mTempRect.bottom = mHeight; - - final boolean isLayoutRtl = isLayoutRtl(); - - if (isLayoutRtl) { - mTempRect.left = 0; - mTempRect.right = mSelectedLeft - 2; - } else { - mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; - mTempRect.right = mSelectedLeft - 2; - } - canvas.drawRect(mTempRect, mDrawPaint); - - if (isLayoutRtl) { - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mWidth; - } - canvas.drawRect(mTempRect, mDrawPaint); - } - - /** - * Draws the week and month day numbers for this week. - * - * @param canvas The canvas to draw on - */ - private void drawWeekNumbersAndDates(Canvas canvas) { - final float textHeight = mDrawPaint.getTextSize(); - final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; - final int nDays = mNumCells; - final int divisor = 2 * nDays; - - mDrawPaint.setTextAlign(Align.CENTER); - mDrawPaint.setTextSize(mDateTextSize); - - int i = 0; - - if (isLayoutRtl()) { - for (; i < nDays - 1; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mUnfocusedMonthDateColor); - int x = (2 * i + 1) * mWidth / divisor; - canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); - } - if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = mWidth - mWidth / divisor; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); - } - } else { - if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = mWidth / divisor; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); - i++; - } - for (; i < nDays; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mUnfocusedMonthDateColor); - int x = (2 * i + 1) * mWidth / divisor; - canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); - } - } - } - - /** - * Draws a horizontal line for separating the weeks. - * - * @param canvas The canvas to draw on. - */ - private void drawWeekSeparators(Canvas canvas) { - // If it is the topmost fully visible child do not draw separator line - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - if (mListView.getChildAt(0).getTop() < 0) { - firstFullyVisiblePosition++; - } - if (firstFullyVisiblePosition == mWeek) { - return; - } - mDrawPaint.setColor(mWeekSeparatorLineColor); - mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); - float startX; - float stopX; - if (isLayoutRtl()) { - startX = 0; - stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; - } else { - startX = mShowWeekNumber ? mWidth / mNumCells : 0; - stopX = mWidth; - } - canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); - } - - /** - * Draws the selected date bars if this week has a selected day. - * - * @param canvas The canvas to draw on - */ - private void drawSelectedDateVerticalBars(Canvas canvas) { - if (!mHasSelectedDay) { - return; - } - mSelectedDateVerticalBar.setBounds( - mSelectedLeft - mSelectedDateVerticalBarWidth / 2, - mWeekSeperatorLineWidth, - mSelectedLeft + mSelectedDateVerticalBarWidth / 2, - mHeight); - mSelectedDateVerticalBar.draw(canvas); - mSelectedDateVerticalBar.setBounds( - mSelectedRight - mSelectedDateVerticalBarWidth / 2, - mWeekSeperatorLineWidth, - mSelectedRight + mSelectedDateVerticalBarWidth / 2, - mHeight); - mSelectedDateVerticalBar.draw(canvas); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mWidth = w; - updateSelectionPositions(); - } - - /** - * This calculates the positions for the selected day lines. - */ - private void updateSelectionPositions() { - if (mHasSelectedDay) { - final boolean isLayoutRtl = isLayoutRtl(); - int selectedPosition = mSelectedDay - mFirstDayOfWeek; - if (selectedPosition < 0) { - selectedPosition += 7; - } - if (mShowWeekNumber && !isLayoutRtl) { - selectedPosition++; - } - if (isLayoutRtl) { - mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; - - } else { - mSelectedLeft = selectedPosition * mWidth / mNumCells; - } - mSelectedRight = mSelectedLeft + mWidth / mNumCells; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView - .getPaddingBottom()) / mShownWeekCount; - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); - } - } - } } diff --git a/core/java/android/widget/CalendarViewLegacyDelegate.java b/core/java/android/widget/CalendarViewLegacyDelegate.java new file mode 100644 index 0000000..2ab3548 --- /dev/null +++ b/core/java/android/widget/CalendarViewLegacyDelegate.java @@ -0,0 +1,1527 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import com.android.internal.R; + +import android.app.Service; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import java.util.Calendar; +import java.util.Locale; + +import libcore.icu.LocaleData; + +/** + * A delegate implementing the legacy CalendarView + */ +class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelegate { + /** + * Default value whether to show week number. + */ + private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; + + /** + * The number of milliseconds in a day.e + */ + private static final long MILLIS_IN_DAY = 86400000L; + + /** + * The number of day in a week. + */ + private static final int DAYS_PER_WEEK = 7; + + /** + * The number of milliseconds in a week. + */ + private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; + + /** + * Affects when the month selection will change while scrolling upe + */ + private static final int SCROLL_HYST_WEEKS = 2; + + /** + * How long the GoTo fling animation should last. + */ + private static final int GOTO_SCROLL_DURATION = 1000; + + /** + * The duration of the adjustment upon a user scroll in milliseconds. + */ + private static final int ADJUSTMENT_SCROLL_DURATION = 500; + + /** + * How long to wait after receiving an onScrollStateChanged notification + * before acting on it. + */ + private static final int SCROLL_CHANGE_DELAY = 40; + + private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; + + private static final int DEFAULT_DATE_TEXT_SIZE = 14; + + private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; + + private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; + + private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; + + private static final int UNSCALED_BOTTOM_BUFFER = 20; + + private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; + + private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; + + private final int mWeekSeperatorLineWidth; + + private int mDateTextSize; + + private Drawable mSelectedDateVerticalBar; + + private final int mSelectedDateVerticalBarWidth; + + private int mSelectedWeekBackgroundColor; + + private int mFocusedMonthDateColor; + + private int mUnfocusedMonthDateColor; + + private int mWeekSeparatorLineColor; + + private int mWeekNumberColor; + + private int mWeekDayTextAppearanceResId; + + private int mDateTextAppearanceResId; + + /** + * The top offset of the weeks list. + */ + private int mListScrollTopOffset = 2; + + /** + * The visible height of a week view. + */ + private int mWeekMinVisibleHeight = 12; + + /** + * The visible height of a week view. + */ + private int mBottomBuffer = 20; + + /** + * The number of shown weeks. + */ + private int mShownWeekCount; + + /** + * Flag whether to show the week number. + */ + private boolean mShowWeekNumber; + + /** + * The number of day per week to be shown. + */ + private int mDaysPerWeek = 7; + + /** + * The friction of the week list while flinging. + */ + private float mFriction = .05f; + + /** + * Scale for adjusting velocity of the week list while flinging. + */ + private float mVelocityScale = 0.333f; + + /** + * The adapter for the weeks list. + */ + private WeeksAdapter mAdapter; + + /** + * The weeks list. + */ + private ListView mListView; + + /** + * The name of the month to display. + */ + private TextView mMonthName; + + /** + * The header with week day names. + */ + private ViewGroup mDayNamesHeader; + + /** + * Cached abbreviations for day of week names. + */ + private String[] mDayNamesShort; + + /** + * Cached full-length day of week names. + */ + private String[] mDayNamesLong; + + /** + * The first day of the week. + */ + private int mFirstDayOfWeek; + + /** + * Which month should be displayed/highlighted [0-11]. + */ + private int mCurrentMonthDisplayed = -1; + + /** + * Used for tracking during a scroll. + */ + private long mPreviousScrollPosition; + + /** + * Used for tracking which direction the view is scrolling. + */ + private boolean mIsScrollingUp = false; + + /** + * The previous scroll state of the weeks ListView. + */ + private int mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; + + /** + * The current scroll state of the weeks ListView. + */ + private int mCurrentScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; + + /** + * Listener for changes in the selected day. + */ + private CalendarView.OnDateChangeListener mOnDateChangeListener; + + /** + * Command for adjusting the position after a scroll/fling. + */ + private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); + + /** + * Temporary instance to avoid multiple instantiations. + */ + private Calendar mTempDate; + + /** + * The first day of the focused month. + */ + private Calendar mFirstDayOfMonth; + + /** + * The start date of the range supported by this picker. + */ + private Calendar mMinDate; + + /** + * The end date of the range supported by this picker. + */ + private Calendar mMaxDate; + + CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.CalendarView, defStyleAttr, defStyleRes); + mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber, + DEFAULT_SHOW_WEEK_NUMBER); + mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, + LocaleData.get(Locale.getDefault()).firstDayOfWeek); + final String minDate = a.getString(R.styleable.CalendarView_minDate); + if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { + parseDate(DEFAULT_MIN_DATE, mMinDate); + } + final String maxDate = a.getString(R.styleable.CalendarView_maxDate); + if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { + parseDate(DEFAULT_MAX_DATE, mMaxDate); + } + if (mMaxDate.before(mMinDate)) { + throw new IllegalArgumentException("Max date cannot be before min date."); + } + mShownWeekCount = a.getInt(R.styleable.CalendarView_shownWeekCount, + DEFAULT_SHOWN_WEEK_COUNT); + mSelectedWeekBackgroundColor = a.getColor( + R.styleable.CalendarView_selectedWeekBackgroundColor, 0); + mFocusedMonthDateColor = a.getColor( + R.styleable.CalendarView_focusedMonthDateColor, 0); + mUnfocusedMonthDateColor = a.getColor( + R.styleable.CalendarView_unfocusedMonthDateColor, 0); + mWeekSeparatorLineColor = a.getColor( + R.styleable.CalendarView_weekSeparatorLineColor, 0); + mWeekNumberColor = a.getColor(R.styleable.CalendarView_weekNumberColor, 0); + mSelectedDateVerticalBar = a.getDrawable( + R.styleable.CalendarView_selectedDateVerticalBar); + + mDateTextAppearanceResId = a.getResourceId( + R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); + updateDateTextSize(); + + mWeekDayTextAppearanceResId = a.getResourceId( + R.styleable.CalendarView_weekDayTextAppearance, + DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); + a.recycle(); + + DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics(); + mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); + mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); + mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_BOTTOM_BUFFER, displayMetrics); + mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); + mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); + + LayoutInflater layoutInflater = (LayoutInflater) mContext + .getSystemService(Service.LAYOUT_INFLATER_SERVICE); + View content = layoutInflater.inflate(R.layout.calendar_view, null, false); + mDelegator.addView(content); + + mListView = (ListView) mDelegator.findViewById(R.id.list); + mDayNamesHeader = (ViewGroup) content.findViewById(R.id.day_names); + mMonthName = (TextView) content.findViewById(R.id.month_name); + + setUpHeader(); + setUpListView(); + setUpAdapter(); + + // go to today or whichever is close to today min or max date + mTempDate.setTimeInMillis(System.currentTimeMillis()); + if (mTempDate.before(mMinDate)) { + goTo(mMinDate, false, true, true); + } else if (mMaxDate.before(mTempDate)) { + goTo(mMaxDate, false, true, true); + } else { + goTo(mTempDate, false, true, true); + } + + mDelegator.invalidate(); + } + + @Override + public void setShownWeekCount(int count) { + if (mShownWeekCount != count) { + mShownWeekCount = count; + mDelegator.invalidate(); + } + } + + @Override + public int getShownWeekCount() { + return mShownWeekCount; + } + + @Override + public void setSelectedWeekBackgroundColor(int color) { + if (mSelectedWeekBackgroundColor != color) { + mSelectedWeekBackgroundColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasSelectedDay) { + weekView.invalidate(); + } + } + } + } + + @Override + public int getSelectedWeekBackgroundColor() { + return mSelectedWeekBackgroundColor; + } + + @Override + public void setFocusedMonthDateColor(int color) { + if (mFocusedMonthDateColor != color) { + mFocusedMonthDateColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasFocusedDay) { + weekView.invalidate(); + } + } + } + } + + @Override + public int getFocusedMonthDateColor() { + return mFocusedMonthDateColor; + } + + @Override + public void setUnfocusedMonthDateColor(int color) { + if (mUnfocusedMonthDateColor != color) { + mUnfocusedMonthDateColor = color; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasUnfocusedDay) { + weekView.invalidate(); + } + } + } + } + + @Override + public int getUnfocusedMonthDateColor() { + return mFocusedMonthDateColor; + } + + @Override + public void setWeekNumberColor(int color) { + if (mWeekNumberColor != color) { + mWeekNumberColor = color; + if (mShowWeekNumber) { + invalidateAllWeekViews(); + } + } + } + + @Override + public int getWeekNumberColor() { + return mWeekNumberColor; + } + + @Override + public void setWeekSeparatorLineColor(int color) { + if (mWeekSeparatorLineColor != color) { + mWeekSeparatorLineColor = color; + invalidateAllWeekViews(); + } + } + + @Override + public int getWeekSeparatorLineColor() { + return mWeekSeparatorLineColor; + } + + @Override + public void setSelectedDateVerticalBar(int resourceId) { + Drawable drawable = mDelegator.getContext().getDrawable(resourceId); + setSelectedDateVerticalBar(drawable); + } + + @Override + public void setSelectedDateVerticalBar(Drawable drawable) { + if (mSelectedDateVerticalBar != drawable) { + mSelectedDateVerticalBar = drawable; + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + WeekView weekView = (WeekView) mListView.getChildAt(i); + if (weekView.mHasSelectedDay) { + weekView.invalidate(); + } + } + } + } + + @Override + public Drawable getSelectedDateVerticalBar() { + return mSelectedDateVerticalBar; + } + + @Override + public void setWeekDayTextAppearance(int resourceId) { + if (mWeekDayTextAppearanceResId != resourceId) { + mWeekDayTextAppearanceResId = resourceId; + setUpHeader(); + } + } + + @Override + public int getWeekDayTextAppearance() { + return mWeekDayTextAppearanceResId; + } + + @Override + public void setDateTextAppearance(int resourceId) { + if (mDateTextAppearanceResId != resourceId) { + mDateTextAppearanceResId = resourceId; + updateDateTextSize(); + invalidateAllWeekViews(); + } + } + + @Override + public int getDateTextAppearance() { + return mDateTextAppearanceResId; + } + + @Override + public void setMinDate(long minDate) { + mTempDate.setTimeInMillis(minDate); + if (isSameDate(mTempDate, mMinDate)) { + return; + } + mMinDate.setTimeInMillis(minDate); + // make sure the current date is not earlier than + // the new min date since the latter is used for + // calculating the indices in the adapter thus + // avoiding out of bounds error + Calendar date = mAdapter.mSelectedDate; + if (date.before(mMinDate)) { + mAdapter.setSelectedDay(mMinDate); + } + // reinitialize the adapter since its range depends on min date + mAdapter.init(); + if (date.before(mMinDate)) { + setDate(mTempDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } + } + + @Override + public long getMinDate() { + return mMinDate.getTimeInMillis(); + } + + @Override + public void setMaxDate(long maxDate) { + mTempDate.setTimeInMillis(maxDate); + if (isSameDate(mTempDate, mMaxDate)) { + return; + } + mMaxDate.setTimeInMillis(maxDate); + // reinitialize the adapter since its range depends on max date + mAdapter.init(); + Calendar date = mAdapter.mSelectedDate; + if (date.after(mMaxDate)) { + setDate(mMaxDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } + } + + @Override + public long getMaxDate() { + return mMaxDate.getTimeInMillis(); + } + + @Override + public void setShowWeekNumber(boolean showWeekNumber) { + if (mShowWeekNumber == showWeekNumber) { + return; + } + mShowWeekNumber = showWeekNumber; + mAdapter.notifyDataSetChanged(); + setUpHeader(); + } + + @Override + public boolean getShowWeekNumber() { + return mShowWeekNumber; + } + + @Override + public void setFirstDayOfWeek(int firstDayOfWeek) { + if (mFirstDayOfWeek == firstDayOfWeek) { + return; + } + mFirstDayOfWeek = firstDayOfWeek; + mAdapter.init(); + mAdapter.notifyDataSetChanged(); + setUpHeader(); + } + + @Override + public int getFirstDayOfWeek() { + return mFirstDayOfWeek; + } + + @Override + public void setDate(long date) { + setDate(date, false, false); + } + + @Override + public void setDate(long date, boolean animate, boolean center) { + mTempDate.setTimeInMillis(date); + if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { + return; + } + goTo(mTempDate, animate, true, center); + } + + @Override + public long getDate() { + return mAdapter.mSelectedDate.getTimeInMillis(); + } + + @Override + public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) { + mOnDateChangeListener = listener; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + setCurrentLocale(newConfig.locale); + } + + /** + * Sets the current locale. + * + * @param locale The current locale. + */ + @Override + protected void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + + mTempDate = getCalendarForLocale(mTempDate, locale); + mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); + mMinDate = getCalendarForLocale(mMinDate, locale); + mMaxDate = getCalendarForLocale(mMaxDate, locale); + } + private void updateDateTextSize() { + TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes( + mDateTextAppearanceResId, R.styleable.TextAppearance); + mDateTextSize = dateTextAppearance.getDimensionPixelSize( + R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); + dateTextAppearance.recycle(); + } + + /** + * Invalidates all week views. + */ + private void invalidateAllWeekViews() { + final int childCount = mListView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = mListView.getChildAt(i); + view.invalidate(); + } + } + + /** + * Gets a calendar for locale bootstrapped with the value of a given calendar. + * + * @param oldCalendar The old calendar. + * @param locale The locale. + */ + private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { + if (oldCalendar == null) { + return Calendar.getInstance(locale); + } else { + final long currentTimeMillis = oldCalendar.getTimeInMillis(); + Calendar newCalendar = Calendar.getInstance(locale); + newCalendar.setTimeInMillis(currentTimeMillis); + return newCalendar; + } + } + + /** + * @return True if the <code>firstDate</code> is the same as the <code> + * secondDate</code>. + */ + private static boolean isSameDate(Calendar firstDate, Calendar secondDate) { + return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) + && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); + } + + /** + * Creates a new adapter if necessary and sets up its parameters. + */ + private void setUpAdapter() { + if (mAdapter == null) { + mAdapter = new WeeksAdapter(mContext); + mAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + if (mOnDateChangeListener != null) { + Calendar selectedDay = mAdapter.getSelectedDay(); + mOnDateChangeListener.onSelectedDayChange(mDelegator, + selectedDay.get(Calendar.YEAR), + selectedDay.get(Calendar.MONTH), + selectedDay.get(Calendar.DAY_OF_MONTH)); + } + } + }); + mListView.setAdapter(mAdapter); + } + + // refresh the view with the new parameters + mAdapter.notifyDataSetChanged(); + } + + /** + * Sets up the strings to be used by the header. + */ + private void setUpHeader() { + mDayNamesShort = new String[mDaysPerWeek]; + mDayNamesLong = new String[mDaysPerWeek]; + for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { + int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; + mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, + DateUtils.LENGTH_SHORTEST); + mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, + DateUtils.LENGTH_LONG); + } + + TextView label = (TextView) mDayNamesHeader.getChildAt(0); + if (mShowWeekNumber) { + label.setVisibility(View.VISIBLE); + } else { + label.setVisibility(View.GONE); + } + for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { + label = (TextView) mDayNamesHeader.getChildAt(i); + if (mWeekDayTextAppearanceResId > -1) { + label.setTextAppearance(mContext, mWeekDayTextAppearanceResId); + } + if (i < mDaysPerWeek + 1) { + label.setText(mDayNamesShort[i - 1]); + label.setContentDescription(mDayNamesLong[i - 1]); + label.setVisibility(View.VISIBLE); + } else { + label.setVisibility(View.GONE); + } + } + mDayNamesHeader.invalidate(); + } + + /** + * Sets all the required fields for the list view. + */ + private void setUpListView() { + // Configure the listview + mListView.setDivider(null); + mListView.setItemsCanFocus(true); + mListView.setVerticalScrollBarEnabled(false); + mListView.setOnScrollListener(new AbsListView.OnScrollListener() { + public void onScrollStateChanged(AbsListView view, int scrollState) { + CalendarViewLegacyDelegate.this.onScrollStateChanged(view, scrollState); + } + + public void onScroll( + AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + CalendarViewLegacyDelegate.this.onScroll(view, firstVisibleItem, + visibleItemCount, totalItemCount); + } + }); + // Make the scrolling behavior nicer + mListView.setFriction(mFriction); + mListView.setVelocityScale(mVelocityScale); + } + + /** + * This moves to the specified time in the view. If the time is not already + * in range it will move the list so that the first of the month containing + * the time is at the top of the view. If the new time is already in view + * the list will not be scrolled unless forceScroll is true. This time may + * optionally be highlighted as selected as well. + * + * @param date The time to move to. + * @param animate Whether to scroll to the given time or just redraw at the + * new location. + * @param setSelected Whether to set the given time as selected. + * @param forceScroll Whether to recenter even if the time is already + * visible. + * + * @throws IllegalArgumentException of the provided date is before the + * range start of after the range end. + */ + private void goTo(Calendar date, boolean animate, boolean setSelected, + boolean forceScroll) { + if (date.before(mMinDate) || date.after(mMaxDate)) { + throw new IllegalArgumentException("Time not between " + mMinDate.getTime() + + " and " + mMaxDate.getTime()); + } + // Find the first and last entirely visible weeks + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + View firstChild = mListView.getChildAt(0); + if (firstChild != null && firstChild.getTop() < 0) { + firstFullyVisiblePosition++; + } + int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; + if (firstChild != null && firstChild.getTop() > mBottomBuffer) { + lastFullyVisiblePosition--; + } + if (setSelected) { + mAdapter.setSelectedDay(date); + } + // Get the week we're going to + int position = getWeeksSinceMinDate(date); + + // Check if the selected day is now outside of our visible range + // and if so scroll to the month that contains it + if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition + || forceScroll) { + mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); + mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); + + setMonthDisplayed(mFirstDayOfMonth); + + // the earliest time we can scroll to is the min date + if (mFirstDayOfMonth.before(mMinDate)) { + position = 0; + } else { + position = getWeeksSinceMinDate(mFirstDayOfMonth); + } + + mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_FLING; + if (animate) { + mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, + GOTO_SCROLL_DURATION); + } else { + mListView.setSelectionFromTop(position, mListScrollTopOffset); + // Perform any after scroll operations that are needed + onScrollStateChanged(mListView, AbsListView.OnScrollListener.SCROLL_STATE_IDLE); + } + } else if (setSelected) { + // Otherwise just set the selection + setMonthDisplayed(date); + } + } + + /** + * Called when a <code>view</code> transitions to a new <code>scrollState + * </code>. + */ + private void onScrollStateChanged(AbsListView view, int scrollState) { + mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); + } + + /** + * Updates the title and selected month if the <code>view</code> has moved to a new + * month. + */ + private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + WeekView child = (WeekView) view.getChildAt(0); + if (child == null) { + return; + } + + // Figure out where we are + long currScroll = + view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); + + // If we have moved since our last call update the direction + if (currScroll < mPreviousScrollPosition) { + mIsScrollingUp = true; + } else if (currScroll > mPreviousScrollPosition) { + mIsScrollingUp = false; + } else { + return; + } + + // Use some hysteresis for checking which month to highlight. This + // causes the month to transition when two full weeks of a month are + // visible when scrolling up, and when the first day in a month reaches + // the top of the screen when scrolling down. + int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; + if (mIsScrollingUp) { + child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); + } else if (offset != 0) { + child = (WeekView) view.getChildAt(offset); + } + + if (child != null) { + // Find out which month we're moving into + int month; + if (mIsScrollingUp) { + month = child.getMonthOfFirstWeekDay(); + } else { + month = child.getMonthOfLastWeekDay(); + } + + // And how it relates to our current highlighted month + int monthDiff; + if (mCurrentMonthDisplayed == 11 && month == 0) { + monthDiff = 1; + } else if (mCurrentMonthDisplayed == 0 && month == 11) { + monthDiff = -1; + } else { + monthDiff = month - mCurrentMonthDisplayed; + } + + // Only switch months if we're scrolling away from the currently + // selected month + if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { + Calendar firstDay = child.getFirstDay(); + if (mIsScrollingUp) { + firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); + } else { + firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); + } + setMonthDisplayed(firstDay); + } + } + mPreviousScrollPosition = currScroll; + mPreviousScrollState = mCurrentScrollState; + } + + /** + * Sets the month displayed at the top of this view based on time. Override + * to add custom events when the title is changed. + * + * @param calendar A day in the new focus month. + */ + private void setMonthDisplayed(Calendar calendar) { + mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); + mAdapter.setFocusMonth(mCurrentMonthDisplayed); + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY + | DateUtils.FORMAT_SHOW_YEAR; + final long millis = calendar.getTimeInMillis(); + String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); + mMonthName.setText(newMonthName); + mMonthName.invalidate(); + } + + /** + * @return Returns the number of weeks between the current <code>date</code> + * and the <code>mMinDate</code>. + */ + private int getWeeksSinceMinDate(Calendar date) { + if (date.before(mMinDate)) { + throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() + + " does not precede toDate: " + date.getTime()); + } + long endTimeMillis = date.getTimeInMillis() + + date.getTimeZone().getOffset(date.getTimeInMillis()); + long startTimeMillis = mMinDate.getTimeInMillis() + + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); + long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) + * MILLIS_IN_DAY; + return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); + } + + /** + * Command responsible for acting upon scroll state changes. + */ + private class ScrollStateRunnable implements Runnable { + private AbsListView mView; + + private int mNewState; + + /** + * Sets up the runnable with a short delay in case the scroll state + * immediately changes again. + * + * @param view The list view that changed state + * @param scrollState The new state it changed to + */ + public void doScrollStateChange(AbsListView view, int scrollState) { + mView = view; + mNewState = scrollState; + mDelegator.removeCallbacks(this); + mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY); + } + + public void run() { + mCurrentScrollState = mNewState; + // Fix the position after a scroll or a fling ends + if (mNewState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE + && mPreviousScrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { + View child = mView.getChildAt(0); + if (child == null) { + // The view is no longer visible, just return + return; + } + int dist = child.getBottom() - mListScrollTopOffset; + if (dist > mListScrollTopOffset) { + if (mIsScrollingUp) { + mView.smoothScrollBy(dist - child.getHeight(), + ADJUSTMENT_SCROLL_DURATION); + } else { + mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); + } + } + } + mPreviousScrollState = mNewState; + } + } + + /** + * <p> + * This is a specialized adapter for creating a list of weeks with + * selectable days. It can be configured to display the week number, start + * the week on a given day, show a reduced number of days, or display an + * arbitrary number of weeks at a time. + * </p> + */ + private class WeeksAdapter extends BaseAdapter implements View.OnTouchListener { + + private int mSelectedWeek; + + private GestureDetector mGestureDetector; + + private int mFocusedMonth; + + private final Calendar mSelectedDate = Calendar.getInstance(); + + private int mTotalWeekCount; + + public WeeksAdapter(Context context) { + mContext = context; + mGestureDetector = new GestureDetector(mContext, new WeeksAdapter.CalendarGestureListener()); + init(); + } + + /** + * Set up the gesture detector and selected time + */ + private void init() { + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); + if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek + || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { + mTotalWeekCount++; + } + notifyDataSetChanged(); + } + + /** + * Updates the selected day and related parameters. + * + * @param selectedDay The time to highlight + */ + public void setSelectedDay(Calendar selectedDay) { + if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) + && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { + return; + } + mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mFocusedMonth = mSelectedDate.get(Calendar.MONTH); + notifyDataSetChanged(); + } + + /** + * @return The selected day of month. + */ + public Calendar getSelectedDay() { + return mSelectedDate; + } + + @Override + public int getCount() { + return mTotalWeekCount; + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + WeekView weekView = null; + if (convertView != null) { + weekView = (WeekView) convertView; + } else { + weekView = new WeekView(mContext); + AbsListView.LayoutParams params = + new AbsListView.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + weekView.setLayoutParams(params); + weekView.setClickable(true); + weekView.setOnTouchListener(this); + } + + int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( + Calendar.DAY_OF_WEEK) : -1; + weekView.init(position, selectedWeekDay, mFocusedMonth); + + return weekView; + } + + /** + * Changes which month is in focus and updates the view. + * + * @param month The month to show as in focus [0-11] + */ + public void setFocusMonth(int month) { + if (mFocusedMonth == month) { + return; + } + mFocusedMonth = month; + notifyDataSetChanged(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { + WeekView weekView = (WeekView) v; + // if we cannot find a day for the given location we are done + if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { + return true; + } + // it is possible that the touched day is outside the valid range + // we draw whole weeks but range end can fall not on the week end + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + return true; + } + onDateTapped(mTempDate); + return true; + } + return false; + } + + /** + * Maintains the same hour/min/sec but moves the day to the tapped day. + * + * @param day The day that was tapped + */ + private void onDateTapped(Calendar day) { + setSelectedDay(day); + setMonthDisplayed(day); + } + + /** + * This is here so we can identify single tap events and set the + * selected day correctly + */ + class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + } + } + + /** + * <p> + * This is a dynamic view for drawing a single week. It can be configured to + * display the week number, start the week on a given day, or show a reduced + * number of days. It is intended for use as a single view within a + * ListView. See {@link WeeksAdapter} for usage. + * </p> + */ + private class WeekView extends View { + + private final Rect mTempRect = new Rect(); + + private final Paint mDrawPaint = new Paint(); + + private final Paint mMonthNumDrawPaint = new Paint(); + + // Cache the number strings so we don't have to recompute them each time + private String[] mDayNumbers; + + // Quick lookup for checking which days are in the focus month + private boolean[] mFocusDay; + + // Whether this view has a focused day. + private boolean mHasFocusedDay; + + // Whether this view has only focused days. + private boolean mHasUnfocusedDay; + + // The first day displayed by this item + private Calendar mFirstDay; + + // The month of the first day in this week + private int mMonthOfFirstWeekDay = -1; + + // The month of the last day in this week + private int mLastWeekDayMonth = -1; + + // The position of this week, equivalent to weeks since the week of Jan + // 1st, 1900 + private int mWeek = -1; + + // Quick reference to the width of this view, matches parent + private int mWidth; + + // The height this view should draw at in pixels, set by height param + private int mHeight; + + // If this view contains the selected day + private boolean mHasSelectedDay = false; + + // Which day is selected [0-6] or -1 if no day is selected + private int mSelectedDay = -1; + + // The number of days + a spot for week number if it is displayed + private int mNumCells; + + // The left edge of the selected day + private int mSelectedLeft = -1; + + // The right edge of the selected day + private int mSelectedRight = -1; + + public WeekView(Context context) { + super(context); + + // Sets up any standard paints that will be used + initilaizePaints(); + } + + /** + * Initializes this week view. + * + * @param weekNumber The number of the week this view represents. The + * week number is a zero based index of the weeks since + * {@link android.widget.CalendarView#getMinDate()}. + * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no + * selected day. + * @param focusedMonth The month that is currently in focus i.e. + * highlighted. + */ + public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { + mSelectedDay = selectedWeekDay; + mHasSelectedDay = mSelectedDay != -1; + mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; + mWeek = weekNumber; + mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); + + mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); + mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); + + // Allocate space for caching the day numbers and focus values + mDayNumbers = new String[mNumCells]; + mFocusDay = new boolean[mNumCells]; + + // If we're showing the week number calculate it based on Monday + int i = 0; + if (mShowWeekNumber) { + mDayNumbers[0] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.WEEK_OF_YEAR)); + i++; + } + + // Now adjust our starting day based on the start day of the week + int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); + mTempDate.add(Calendar.DAY_OF_MONTH, diff); + + mFirstDay = (Calendar) mTempDate.clone(); + mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); + + mHasUnfocusedDay = true; + for (; i < mNumCells; i++) { + final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); + mFocusDay[i] = isFocusedDay; + mHasFocusedDay |= isFocusedDay; + mHasUnfocusedDay &= !isFocusedDay; + // do not draw dates outside the valid range to avoid user confusion + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + mDayNumbers[i] = ""; + } else { + mDayNumbers[i] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.DAY_OF_MONTH)); + } + mTempDate.add(Calendar.DAY_OF_MONTH, 1); + } + // We do one extra add at the end of the loop, if that pushed us to + // new month undo it + if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { + mTempDate.add(Calendar.DAY_OF_MONTH, -1); + } + mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); + + updateSelectionPositions(); + } + + /** + * Initialize the paint instances. + */ + private void initilaizePaints() { + mDrawPaint.setFakeBoldText(false); + mDrawPaint.setAntiAlias(true); + mDrawPaint.setStyle(Paint.Style.FILL); + + mMonthNumDrawPaint.setFakeBoldText(true); + mMonthNumDrawPaint.setAntiAlias(true); + mMonthNumDrawPaint.setStyle(Paint.Style.FILL); + mMonthNumDrawPaint.setTextAlign(Paint.Align.CENTER); + mMonthNumDrawPaint.setTextSize(mDateTextSize); + } + + /** + * Returns the month of the first day in this week. + * + * @return The month the first day of this view is in. + */ + public int getMonthOfFirstWeekDay() { + return mMonthOfFirstWeekDay; + } + + /** + * Returns the month of the last day in this week + * + * @return The month the last day of this view is in + */ + public int getMonthOfLastWeekDay() { + return mLastWeekDayMonth; + } + + /** + * Returns the first day in this view. + * + * @return The first day in the view. + */ + public Calendar getFirstDay() { + return mFirstDay; + } + + /** + * Calculates the day that the given x position is in, accounting for + * week number. + * + * @param x The x position of the touch event. + * @return True if a day was found for the given location. + */ + public boolean getDayFromLocation(float x, Calendar outCalendar) { + final boolean isLayoutRtl = isLayoutRtl(); + + int start; + int end; + + if (isLayoutRtl) { + start = 0; + end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + start = mShowWeekNumber ? mWidth / mNumCells : 0; + end = mWidth; + } + + if (x < start || x > end) { + outCalendar.clear(); + return false; + } + + // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels + int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); + + if (isLayoutRtl) { + dayPosition = mDaysPerWeek - 1 - dayPosition; + } + + outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); + outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); + + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + drawBackground(canvas); + drawWeekNumbersAndDates(canvas); + drawWeekSeparators(canvas); + drawSelectedDateVerticalBars(canvas); + } + + /** + * This draws the selection highlight if a day is selected in this week. + * + * @param canvas The canvas to draw on + */ + private void drawBackground(Canvas canvas) { + if (!mHasSelectedDay) { + return; + } + mDrawPaint.setColor(mSelectedWeekBackgroundColor); + + mTempRect.top = mWeekSeperatorLineWidth; + mTempRect.bottom = mHeight; + + final boolean isLayoutRtl = isLayoutRtl(); + + if (isLayoutRtl) { + mTempRect.left = 0; + mTempRect.right = mSelectedLeft - 2; + } else { + mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; + mTempRect.right = mSelectedLeft - 2; + } + canvas.drawRect(mTempRect, mDrawPaint); + + if (isLayoutRtl) { + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mWidth; + } + canvas.drawRect(mTempRect, mDrawPaint); + } + + /** + * Draws the week and month day numbers for this week. + * + * @param canvas The canvas to draw on + */ + private void drawWeekNumbersAndDates(Canvas canvas) { + final float textHeight = mDrawPaint.getTextSize(); + final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; + final int nDays = mNumCells; + final int divisor = 2 * nDays; + + mDrawPaint.setTextAlign(Paint.Align.CENTER); + mDrawPaint.setTextSize(mDateTextSize); + + int i = 0; + + if (isLayoutRtl()) { + for (; i < nDays - 1; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); + } + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth - mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + } + } else { + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + i++; + } + for (; i < nDays; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + } + } + } + + /** + * Draws a horizontal line for separating the weeks. + * + * @param canvas The canvas to draw on. + */ + private void drawWeekSeparators(Canvas canvas) { + // If it is the topmost fully visible child do not draw separator line + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + if (mListView.getChildAt(0).getTop() < 0) { + firstFullyVisiblePosition++; + } + if (firstFullyVisiblePosition == mWeek) { + return; + } + mDrawPaint.setColor(mWeekSeparatorLineColor); + mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); + float startX; + float stopX; + if (isLayoutRtl()) { + startX = 0; + stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + startX = mShowWeekNumber ? mWidth / mNumCells : 0; + stopX = mWidth; + } + canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); + } + + /** + * Draws the selected date bars if this week has a selected day. + * + * @param canvas The canvas to draw on + */ + private void drawSelectedDateVerticalBars(Canvas canvas) { + if (!mHasSelectedDay) { + return; + } + mSelectedDateVerticalBar.setBounds( + mSelectedLeft - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedLeft + mSelectedDateVerticalBarWidth / 2, + mHeight); + mSelectedDateVerticalBar.draw(canvas); + mSelectedDateVerticalBar.setBounds( + mSelectedRight - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedRight + mSelectedDateVerticalBarWidth / 2, + mHeight); + mSelectedDateVerticalBar.draw(canvas); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mWidth = w; + updateSelectionPositions(); + } + + /** + * This calculates the positions for the selected day lines. + */ + private void updateSelectionPositions() { + if (mHasSelectedDay) { + final boolean isLayoutRtl = isLayoutRtl(); + int selectedPosition = mSelectedDay - mFirstDayOfWeek; + if (selectedPosition < 0) { + selectedPosition += 7; + } + if (mShowWeekNumber && !isLayoutRtl) { + selectedPosition++; + } + if (isLayoutRtl) { + mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; + + } else { + mSelectedLeft = selectedPosition * mWidth / mNumCells; + } + mSelectedRight = mSelectedLeft + mWidth / mNumCells; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView + .getPaddingBottom()) / mShownWeekCount; + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); + } + } + +} diff --git a/core/java/android/widget/CalendarViewMaterialDelegate.java b/core/java/android/widget/CalendarViewMaterialDelegate.java new file mode 100644 index 0000000..b0f3740 --- /dev/null +++ b/core/java/android/widget/CalendarViewMaterialDelegate.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import com.android.internal.R; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.MathUtils; + +import java.util.Calendar; +import java.util.Locale; + +import libcore.icu.LocaleData; + +class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDelegate { + private final DayPickerView mDayPickerView; + + private CalendarView.OnDateChangeListener mOnDateChangeListener; + + public CalendarViewMaterialDelegate(CalendarView delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.CalendarView, defStyleAttr, defStyleRes); + final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, + LocaleData.get(Locale.getDefault()).firstDayOfWeek); + + final long minDate = parseDateToMillis(a.getString( + R.styleable.CalendarView_minDate), DEFAULT_MIN_DATE); + final long maxDate = parseDateToMillis(a.getString( + R.styleable.CalendarView_maxDate), DEFAULT_MAX_DATE); + if (maxDate < minDate) { + throw new IllegalArgumentException("max date cannot be before min date"); + } + + final long setDate = MathUtils.constrain(System.currentTimeMillis(), minDate, maxDate); + final int dateTextAppearanceResId = a.getResourceId( + R.styleable.CalendarView_dateTextAppearance, + R.style.TextAppearance_DeviceDefault_Small); + + a.recycle(); + + mDayPickerView = new DayPickerView(context); + mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); + mDayPickerView.setCalendarTextAppearance(dateTextAppearanceResId); + mDayPickerView.setMinDate(minDate); + mDayPickerView.setMaxDate(maxDate); + mDayPickerView.setDate(setDate, false, true); + mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener); + + delegator.addView(mDayPickerView); + } + + private long parseDateToMillis(String dateStr, String defaultDateStr) { + final Calendar tempCalendar = Calendar.getInstance(); + if (TextUtils.isEmpty(dateStr) || !parseDate(dateStr, tempCalendar)) { + parseDate(defaultDateStr, tempCalendar); + } + return tempCalendar.getTimeInMillis(); + } + + @Override + public void setShownWeekCount(int count) { + // Deprecated. + } + + @Override + public int getShownWeekCount() { + // Deprecated. + return 0; + } + + @Override + public void setSelectedWeekBackgroundColor(int color) { + // TODO: Should use a ColorStateList. Deprecate? + } + + @Override + public int getSelectedWeekBackgroundColor() { + return 0; + } + + @Override + public void setFocusedMonthDateColor(int color) { + // TODO: Should use a ColorStateList. Deprecate? + } + + @Override + public int getFocusedMonthDateColor() { + return 0; + } + + @Override + public void setUnfocusedMonthDateColor(int color) { + // TODO: Should use a ColorStateList. Deprecate? + } + + @Override + public int getUnfocusedMonthDateColor() { + return 0; + } + + @Override + public void setWeekDayTextAppearance(int resourceId) { + + } + + @Override + public int getWeekDayTextAppearance() { + return 0; + } + + @Override + public void setDateTextAppearance(int resourceId) { + + } + + @Override + public int getDateTextAppearance() { + return 0; + } + + @Override + public void setWeekNumberColor(int color) { + // Deprecated. + } + + @Override + public int getWeekNumberColor() { + // Deprecated. + return 0; + } + + @Override + public void setWeekSeparatorLineColor(int color) { + // Deprecated. + } + + @Override + public int getWeekSeparatorLineColor() { + // Deprecated. + return 0; + } + + @Override + public void setSelectedDateVerticalBar(int resourceId) { + // Deprecated. + } + + @Override + public void setSelectedDateVerticalBar(Drawable drawable) { + // Deprecated. + } + + @Override + public Drawable getSelectedDateVerticalBar() { + // Deprecated. + return null; + } + + @Override + public void setMinDate(long minDate) { + mDayPickerView.setMinDate(minDate); + } + + @Override + public long getMinDate() { + return mDayPickerView.getMinDate(); + } + + @Override + public void setMaxDate(long maxDate) { + mDayPickerView.setMaxDate(maxDate); + } + + @Override + public long getMaxDate() { + return mDayPickerView.getMaxDate(); + } + + @Override + public void setShowWeekNumber(boolean showWeekNumber) { + // Deprecated. + } + + @Override + public boolean getShowWeekNumber() { + // Deprecated. + return false; + } + + @Override + public void setFirstDayOfWeek(int firstDayOfWeek) { + mDayPickerView.setFirstDayOfWeek(firstDayOfWeek); + } + + @Override + public int getFirstDayOfWeek() { + return mDayPickerView.getFirstDayOfWeek(); + } + + @Override + public void setDate(long date) { + mDayPickerView.setDate(date, true, false); + } + + @Override + public void setDate(long date, boolean animate, boolean center) { + mDayPickerView.setDate(date, animate, center); + } + + @Override + public long getDate() { + return mDayPickerView.getDate(); + } + + @Override + public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) { + mOnDateChangeListener = listener; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // Nothing to do here, configuration changes are already propagated + // by ViewGroup. + } + + private final DayPickerView.OnDaySelectedListener mOnDaySelectedListener = + new DayPickerView.OnDaySelectedListener() { + @Override + public void onDaySelected(DayPickerView view, Calendar day) { + if (mOnDateChangeListener != null) { + final int year = day.get(Calendar.YEAR); + final int month = day.get(Calendar.MONTH); + final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH); + mOnDateChangeListener.onSelectedDayChange(mDelegator, year, month, dayOfMonth); + } + } + }; +} diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 3e4eb02..0ca08e1 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -51,12 +51,19 @@ import java.util.TimeZone; import libcore.icu.ICU; /** - * This class is a widget for selecting a date. The date can be selected by a - * year, month, and day spinners or a {@link CalendarView}. The set of spinners - * and the calendar view are automatically synchronized. The client can - * customize whether only the spinners, or only the calendar view, or both to be - * displayed. Also the minimal and maximal date from which dates to be selected - * can be customized. + * Provides a widget for selecting a date. + * <p> + * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is + * set to {@code spinner}, the date can be selected using year, month, and day + * spinners or a {@link CalendarView}. The set of spinners and the calendar + * view are automatically synchronized. The client can customize whether only + * the spinners, or only the calendar view, or both to be displayed. + * </p> + * <p> + * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is + * set to {@code calendar}, the month and day can be selected using a + * calendar-style view while the year can be selected separately using a list. + * </p> * <p> * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a> * guide. @@ -80,6 +87,7 @@ import libcore.icu.ICU; * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance * @attr ref android.R.styleable#DatePicker_yearListSelectorColor * @attr ref android.R.styleable#DatePicker_calendarTextColor + * @attr ref android.R.styleable#DatePicker_datePickerMode */ @Widget public class DatePicker extends FrameLayout { @@ -357,6 +365,10 @@ public class DatePicker extends FrameLayout { /** * Gets the {@link CalendarView}. + * <p> + * This method returns {@code null} when the + * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set + * to {@code calendar}. * * @return The calendar view. * @see #getCalendarViewShown() @@ -367,6 +379,10 @@ public class DatePicker extends FrameLayout { /** * Sets whether the {@link CalendarView} is shown. + * <p> + * Calling this method has no effect when the + * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set + * to {@code calendar}. * * @param shown True if the calendar view is to be shown. */ diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index cf3dbab..820bf78 100644 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -185,8 +185,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i mDayPickerView = new DayPickerView(mContext); mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek); - mDayPickerView.setRange(mMinDate, mMaxDate); - mDayPickerView.setDay(mCurrentDate); + mDayPickerView.setMinDate(mMinDate.getTimeInMillis()); + mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis()); + mDayPickerView.setDate(mCurrentDate.getTimeInMillis()); mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener); mYearPickerView = new YearPickerView(mContext); @@ -336,7 +337,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i switch (viewIndex) { case MONTH_AND_DAY_VIEW: - mDayPickerView.setDay(getSelectedDay()); + mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); if (mCurrentView != viewIndex) { mMonthAndDayLayout.setSelected(true); mHeaderYearTextView.setSelected(false); @@ -414,7 +415,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i updateDisplay(false); } mMinDate.setTimeInMillis(minDate); - mDayPickerView.setRange(mMinDate, mMaxDate); + mDayPickerView.setMinDate(minDate); mYearPickerView.setRange(mMinDate, mMaxDate); } @@ -436,7 +437,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i updateDisplay(false); } mMaxDate.setTimeInMillis(maxDate); - mDayPickerView.setRange(mMinDate, mMaxDate); + mDayPickerView.setMaxDate(maxDate); mYearPickerView.setRange(mMinDate, mMaxDate); } @@ -616,7 +617,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i listener.onDateChanged(); } - mDayPickerView.setDay(getSelectedDay()); + mDayPickerView.setDate(getSelectedDay().getTimeInMillis()); } @Override diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java index 443884a..db17df7 100644 --- a/core/java/android/widget/DateTimeView.java +++ b/core/java/android/widget/DateTimeView.java @@ -156,7 +156,7 @@ public class DateTimeView extends TextView { format = getTimeFormat(); break; case SHOW_MONTH_DAY_YEAR: - format = getDateFormat(); + format = DateFormat.getDateInstance(DateFormat.SHORT); break; default: throw new RuntimeException("unknown display value: " + display); @@ -196,21 +196,6 @@ public class DateTimeView extends TextView { return android.text.format.DateFormat.getTimeFormat(getContext()); } - private DateFormat getDateFormat() { - String format = Settings.System.getString(getContext().getContentResolver(), - Settings.System.DATE_FORMAT); - if (format == null || "".equals(format)) { - return DateFormat.getDateInstance(DateFormat.SHORT); - } else { - try { - return new SimpleDateFormat(format); - } catch (IllegalArgumentException e) { - // If we tried to use a bad format string, fall back to a default. - return DateFormat.getDateInstance(DateFormat.SHORT); - } - } - } - void clearFormatAndUpdate() { mLastFormat = null; update(); @@ -283,14 +268,10 @@ public class DateTimeView extends TextView { filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); context.registerReceiver(mReceiver, filter); - - final Uri uri = Settings.System.getUriFor(Settings.System.DATE_FORMAT); - context.getContentResolver().registerContentObserver(uri, true, mObserver); } void unregister(Context context) { context.unregisterReceiver(mReceiver); - context.getContentResolver().unregisterContentObserver(mObserver); } } } diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index 6cb1c9d..7db3fb9 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -58,6 +58,8 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { private Calendar mMinDate = Calendar.getInstance(); private Calendar mMaxDate = Calendar.getInstance(); + private Calendar mTempCalendar; + private OnDaySelectedListener mOnDaySelectedListener; // which month should be displayed/highlighted [0-11] @@ -77,28 +79,65 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { setDrawSelectorOnTop(false); setUpListView(); - goTo(mSelectedDay, false, true, true); + goTo(mSelectedDay.getTimeInMillis(), false, false, true); mAdapter.setOnDaySelectedListener(mProxyOnDaySelectedListener); } - public void setDay(Calendar day) { - goTo(day, false, true, true); + /** + * Sets the currently selected date to the specified timestamp. Jumps + * immediately to the new date. To animate to the new date, use + * {@link #setDate(long, boolean, boolean)}. + * + * @param timeInMillis + */ + public void setDate(long timeInMillis) { + setDate(timeInMillis, false, true); + } + + public void setDate(long timeInMillis, boolean animate, boolean forceScroll) { + goTo(timeInMillis, animate, true, forceScroll); + } + + public long getDate() { + return mSelectedDay.getTimeInMillis(); } public void setFirstDayOfWeek(int firstDayOfWeek) { mAdapter.setFirstDayOfWeek(firstDayOfWeek); } - public void setRange(Calendar minDate, Calendar maxDate) { - mMinDate.setTimeInMillis(minDate.getTimeInMillis()); - mMaxDate.setTimeInMillis(maxDate.getTimeInMillis()); + public int getFirstDayOfWeek() { + return mAdapter.getFirstDayOfWeek(); + } + + public void setMinDate(long timeInMillis) { + mMinDate.setTimeInMillis(timeInMillis); + onRangeChanged(); + } + + public long getMinDate() { + return mMinDate.getTimeInMillis(); + } + + public void setMaxDate(long timeInMillis) { + mMaxDate.setTimeInMillis(timeInMillis); + onRangeChanged(); + } + public long getMaxDate() { + return mMaxDate.getTimeInMillis(); + } + + /** + * Handles changes to date range. + */ + public void onRangeChanged() { mAdapter.setRange(mMinDate, mMaxDate); // Changing the min/max date changes the selection position since we - // don't really have stable IDs. - goTo(mSelectedDay, false, true, true); + // don't really have stable IDs. Jumps immediately to the new position. + goTo(mSelectedDay.getTimeInMillis(), false, false, true); } /** @@ -136,12 +175,20 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { return diffMonths; } - private int getPositionFromDay(Calendar day) { + private int getPositionFromDay(long timeInMillis) { final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate); - final int diffMonth = getDiffMonths(mMinDate, day); + final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis)); return MathUtils.constrain(diffMonth, 0, diffMonthMax); } + private Calendar getTempCalendarForTime(long timeInMillis) { + if (mTempCalendar == null) { + mTempCalendar = Calendar.getInstance(); + } + mTempCalendar.setTimeInMillis(timeInMillis); + return mTempCalendar; + } + /** * This moves to the specified time in the view. If the time is not already * in range it will move the list so that the first of the month containing @@ -157,14 +204,14 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { * visible * @return Whether or not the view animated to the new location */ - private boolean goTo(Calendar day, boolean animate, boolean setSelected, boolean forceScroll) { + private boolean goTo(long day, boolean animate, boolean setSelected, boolean forceScroll) { // Set the selected day if (setSelected) { - mSelectedDay.setTimeInMillis(day.getTimeInMillis()); + mSelectedDay.setTimeInMillis(day); } - mTempDay.setTimeInMillis(day.getTimeInMillis()); + mTempDay.setTimeInMillis(day); final int position = getPositionFromDay(day); View child; @@ -258,6 +305,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { mAdapter.setCalendarTextColor(colors); } + void setCalendarTextAppearance(int resId) { + mAdapter.setCalendarTextAppearance(resId); + } + protected class ScrollStateRunnable implements Runnable { private int mNewState; private View mParent; @@ -415,7 +466,7 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { } private String getMonthAndYearString(Calendar day) { - StringBuffer sbuf = new StringBuffer(); + final StringBuilder sbuf = new StringBuilder(); sbuf.append(day.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())); sbuf.append(" "); sbuf.append(mYearFormat.format(day.getTime())); @@ -429,8 +480,8 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); } /** @@ -474,7 +525,7 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { // Go to that month. announceForAccessibility(getMonthAndYearString(day)); - goTo(day, true, false, true); + goTo(day.getTimeInMillis(), true, false, true); mPerformingScroll = true; return true; } diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 0687905..fe143de 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -106,6 +106,9 @@ class FastScroller { */ private final int[] mPreviewResId = new int[2]; + /** The minimum touch target size in pixels. */ + private final int mMinimumTouchTarget; + /** * Padding in pixels around the preview text. Applied as layout margins to * the preview text and padding to the preview image. @@ -254,6 +257,9 @@ class FastScroller { mPrimaryText = createPreviewTextView(context); mSecondaryText = createPreviewTextView(context); + mMinimumTouchTarget = listView.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.fast_scroller_minimum_touch_target); + setStyle(styleResId); final ViewGroupOverlay overlay = listView.getOverlay(); @@ -1194,17 +1200,37 @@ class FastScroller { return MathUtils.constrain((y - offset) / range, 0f, 1f); } + /** + * Calculates the thumb position based on the visible items. + * + * @param firstVisibleItem First visible item, >= 0. + * @param visibleItemCount Number of visible items, >= 0. + * @param totalItemCount Total number of items, >= 0. + * @return + */ private float getPosFromItemCount( int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (mSectionIndexer == null || mListAdapter == null) { + final SectionIndexer sectionIndexer = mSectionIndexer; + if (sectionIndexer == null || mListAdapter == null) { getSectionsFromIndexer(); } - final boolean hasSections = mSectionIndexer != null && mSections != null + if (visibleItemCount == 0 || totalItemCount == 0) { + // No items are visible. + return 0; + } + + final boolean hasSections = sectionIndexer != null && mSections != null && mSections.length > 0; if (!hasSections || !mMatchDragPosition) { - return (float) firstVisibleItem / (totalItemCount - visibleItemCount); + if (visibleItemCount == totalItemCount) { + // All items are visible. + return 0; + } else { + return (float) firstVisibleItem / (totalItemCount - visibleItemCount); + } } + // Ignore headers. firstVisibleItem -= mHeaderCount; if (firstVisibleItem < 0) { @@ -1222,14 +1248,14 @@ class FastScroller { } // Number of rows in this section. - final int section = mSectionIndexer.getSectionForPosition(firstVisibleItem); - final int sectionPos = mSectionIndexer.getPositionForSection(section); + final int section = sectionIndexer.getSectionForPosition(firstVisibleItem); + final int sectionPos = sectionIndexer.getPositionForSection(section); final int sectionCount = mSections.length; final int positionsInSection; if (section < sectionCount - 1) { final int nextSectionPos; if (section + 1 < sectionCount) { - nextSectionPos = mSectionIndexer.getPositionForSection(section + 1); + nextSectionPos = sectionIndexer.getPositionForSection(section + 1); } else { nextSectionPos = totalItemCount - 1; } @@ -1454,10 +1480,18 @@ class FastScroller { } private boolean isPointInsideX(float x) { + final float offset = mThumbImage.getTranslationX(); + final float left = mThumbImage.getLeft() + offset; + final float right = mThumbImage.getRight() + offset; + + // Apply the minimum touch target size. + final float targetSizeDiff = mMinimumTouchTarget - (right - left); + final float adjust = targetSizeDiff > 0 ? targetSizeDiff : 0; + if (mLayoutFromRight) { - return x >= mThumbImage.getLeft(); + return x >= mThumbImage.getLeft() - adjust; } else { - return x <= mThumbImage.getRight(); + return x <= mThumbImage.getRight() + adjust; } } @@ -1465,7 +1499,12 @@ class FastScroller { final float offset = mThumbImage.getTranslationY(); final float top = mThumbImage.getTop() + offset; final float bottom = mThumbImage.getBottom() + offset; - return y >= top && y <= bottom; + + // Apply the minimum touch target size. + final float targetSizeDiff = mMinimumTouchTarget - (bottom - top); + final float adjust = targetSizeDiff > 0 ? targetSizeDiff / 2 : 0; + + return y >= (top - adjust) && y <= (bottom + adjust); } /** diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index defc26c..161ae7e 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -1613,7 +1613,11 @@ public class GridLayout extends ViewGroup { equivalent to the single-source shortest paths problem on a digraph, for which the O(n^2) Bellman-Ford algorithm the most commonly used general solution. */ - private void solve(Arc[] arcs, int[] locations) { + private boolean solve(Arc[] arcs, int[] locations) { + return solve(arcs, locations, true); + } + + private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) { String axisName = horizontal ? "horizontal" : "vertical"; int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. boolean[] originalCulprits = null; @@ -1631,10 +1635,14 @@ public class GridLayout extends ViewGroup { if (originalCulprits != null) { logError(axisName, arcs, originalCulprits); } - return; + return true; } } + if (!modifyOnError) { + return false; // cannot solve with these constraints + } + boolean[] culprits = new boolean[arcs.length]; for (int i = 0; i < N; i++) { for (int j = 0, length = arcs.length; j < length; j++) { @@ -1658,6 +1666,7 @@ public class GridLayout extends ViewGroup { } } } + return true; } private void computeMargins(boolean leading) { @@ -1697,8 +1706,8 @@ public class GridLayout extends ViewGroup { return trailingMargins; } - private void solve(int[] a) { - solve(getArcs(), a); + private boolean solve(int[] a) { + return solve(getArcs(), a); } private boolean computeHasWeights() { @@ -1740,28 +1749,18 @@ public class GridLayout extends ViewGroup { return deltas; } - private void shareOutDelta() { - int totalDelta = 0; - float totalWeight = 0; + private void shareOutDelta(int totalDelta, float totalWeight) { + Arrays.fill(deltas, 0); for (int i = 0, N = getChildCount(); i < N; i++) { View c = getChildAt(i); LayoutParams lp = getLayoutParams(c); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; float weight = spec.weight; if (weight != 0) { - int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i]; - totalDelta += delta; - totalWeight += weight; - } - } - for (int i = 0, N = getChildCount(); i < N; i++) { - LayoutParams lp = getLayoutParams(getChildAt(i)); - Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; - float weight = spec.weight; - if (weight != 0) { int delta = Math.round((weight * totalDelta / totalWeight)); deltas[i] = delta; - // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end + // the two adjustments below are to counter the above rounding and avoid + // off-by-ones at the end totalDelta -= delta; totalWeight -= weight; } @@ -1771,12 +1770,46 @@ public class GridLayout extends ViewGroup { private void solveAndDistributeSpace(int[] a) { Arrays.fill(getDeltas(), 0); solve(a); - shareOutDelta(); - arcsValid = false; - forwardLinksValid = false; - backwardLinksValid = false; - groupBoundsValid = false; - solve(a); + int deltaMax = parentMin.value * getChildCount() + 1; //exclusive + if (deltaMax < 2) { + return; //don't have any delta to distribute + } + int deltaMin = 0; //inclusive + + float totalWeight = calculateTotalWeight(); + + int validDelta = -1; //delta for which a solution exists + boolean validSolution = true; + // do a binary search to find the max delta that won't conflict with constraints + while(deltaMin < deltaMax) { + final int delta = (deltaMin + deltaMax) / 2; + invalidateValues(); + shareOutDelta(delta, totalWeight); + validSolution = solve(getArcs(), a, false); + if (validSolution) { + validDelta = delta; + deltaMin = delta + 1; + } else { + deltaMax = delta; + } + } + if (validDelta > 0 && !validSolution) { + // last solution was not successful but we have a successful one. Use it. + invalidateValues(); + shareOutDelta(validDelta, totalWeight); + solve(a); + } + } + + private float calculateTotalWeight() { + float totalWeight = 0f; + for (int i = 0, N = getChildCount(); i < N; i++) { + View c = getChildAt(i); + LayoutParams lp = getLayoutParams(c); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + totalWeight += spec.weight; + } + return totalWeight; } private void computeLocations(int[] a) { diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 396c0b9..5419ab6 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -378,10 +378,10 @@ public class PopupWindow { } /** - * Set the flag on popup to ignore cheek press event; by default this flag + * Set the flag on popup to ignore cheek press events; by default this flag * is set to false - * which means the pop wont ignore cheek press dispatch events. - * + * which means the popup will not ignore cheek press dispatch events. + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 887a93b..de1bbc7 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -227,8 +227,6 @@ public class ProgressBar extends View { private long mUiThreadId; private boolean mShouldStartAnimationDrawable; - private float mAnimationPosition; - private boolean mInDrawing; private boolean mAttached; private boolean mRefreshIsPosted; @@ -246,7 +244,7 @@ public class ProgressBar extends View { public ProgressBar(Context context) { this(context, null); } - + public ProgressBar(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.progressBarStyle); } @@ -263,9 +261,9 @@ public class ProgressBar extends View { final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes); - + mNoInvalidate = true; - + final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); if (progressDrawable != null) { // Calling this method can set mMaxHeight, make sure the corresponding @@ -284,11 +282,11 @@ public class ProgressBar extends View { mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior); final int resID = a.getResourceId( - com.android.internal.R.styleable.ProgressBar_interpolator, + com.android.internal.R.styleable.ProgressBar_interpolator, android.R.anim.linear_interpolator); // default to linear interpolator if (resID > 0) { setInterpolator(context, resID); - } + } setMax(a.getInt(R.styleable.ProgressBar_max, mMax)); @@ -401,12 +399,12 @@ public class ProgressBar extends View { * traverse layer and state list drawables. */ private Drawable tileify(Drawable drawable, boolean clip) { - + if (drawable instanceof LayerDrawable) { LayerDrawable background = (LayerDrawable) drawable; final int N = background.getNumberOfLayers(); Drawable[] outDrawables = new Drawable[N]; - + for (int i = 0; i < N; i++) { int id = background.getId(i); outDrawables[i] = tileify(background.getDrawable(i), @@ -414,13 +412,13 @@ public class ProgressBar extends View { } LayerDrawable newBg = new LayerDrawable(outDrawables); - + for (int i = 0; i < N; i++) { newBg.setId(i, background.getId(i)); } - + return newBg; - + } else if (drawable instanceof StateListDrawable) { StateListDrawable in = (StateListDrawable) drawable; StateListDrawable out = new StateListDrawable(); @@ -429,7 +427,7 @@ public class ProgressBar extends View { out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); } return out; - + } else if (drawable instanceof BitmapDrawable) { final BitmapDrawable bitmap = (BitmapDrawable) drawable; final Bitmap tileBitmap = bitmap.getBitmap(); @@ -450,7 +448,7 @@ public class ProgressBar extends View { return clip ? new ClipDrawable( shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable; } - + return drawable; } @@ -458,7 +456,7 @@ public class ProgressBar extends View { final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; return new RoundRectShape(roundedCorners, null, null); } - + /** * Convert a AnimationDrawable for use as a barberpole animation. * Each frame of the animation is wrapped in a ClipDrawable and @@ -470,7 +468,7 @@ public class ProgressBar extends View { final int N = background.getNumberOfFrames(); AnimationDrawable newBg = new AnimationDrawable(); newBg.setOneShot(background.isOneShot()); - + for (int i = 0; i < N; i++) { Drawable frame = tileify(background.getFrame(i), true); frame.setLevel(10000); @@ -481,7 +479,7 @@ public class ProgressBar extends View { } return drawable; } - + /** * <p> * Initialize the progress bar's default values: @@ -522,7 +520,7 @@ public class ProgressBar extends View { * <p>Change the indeterminate mode for this progress bar. In indeterminate * mode, the progress is ignored and the progress bar shows an infinite * animation instead.</p> - * + * * If this progress bar's style only supports indeterminate mode (such as the circular * progress bars), then this will be ignored. * @@ -701,7 +699,7 @@ public class ProgressBar extends View { setIndeterminateDrawable(d); } - + /** * <p>Get the drawable used to draw the progress bar in * progress mode.</p> @@ -1137,7 +1135,7 @@ public class ProgressBar extends View { setProgressDrawable(d); } - + /** * @return The drawable currently used to draw the progress bar */ @@ -1188,7 +1186,7 @@ public class ProgressBar extends View { final int count = mRefreshData.size(); for (int i = 0; i < count; i++) { final RefreshData rd = mRefreshData.get(i); - doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); + doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); rd.recycle(); } mRefreshData.clear(); @@ -1203,12 +1201,10 @@ public class ProgressBar extends View { new SynchronizedPool<RefreshData>(POOL_MAX); public int id; - public float progress; + public int progress; public boolean fromUser; - public boolean animate; - public static RefreshData obtain(int id, float progress, boolean fromUser, - boolean animate) { + public static RefreshData obtain(int id, int progress, boolean fromUser) { RefreshData rd = sPool.acquire(); if (rd == null) { rd = new RefreshData(); @@ -1216,10 +1212,9 @@ public class ProgressBar extends View { rd.id = id; rd.progress = progress; rd.fromUser = fromUser; - rd.animate = animate; return rd; } - + public void recycle() { sPool.release(this); } @@ -1243,19 +1238,9 @@ public class ProgressBar extends View { layer.setTintMode(tintMode); } - private float getScale(float progress) { - return mMax > 0 ? progress / (float) mMax : 0; - } - - private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, + private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, boolean callBackToApp) { - doRefreshProgress(id, progress, fromUser, callBackToApp, false); - } - - private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, - boolean callBackToApp, boolean animate) { - float scale = getScale(progress); - + float scale = mMax > 0 ? (float) progress / (float) mMax : 0; final Drawable d = mCurrentDrawable; if (d != null) { Drawable progressDrawable = null; @@ -1272,65 +1257,27 @@ public class ProgressBar extends View { } else { invalidate(); } - - if (id == R.id.progress) { - if (animate) { - onAnimatePosition(scale, fromUser); - } else if (callBackToApp) { - onProgressRefresh(scale, fromUser); - } + + if (callBackToApp && id == R.id.progress) { + onProgressRefresh(scale, fromUser); } } - /** - * Called when a ProgressBar is animating its position. - * - * @param scale Current position/progress between 0 and 1. - * @param fromUser True if the progress change was initiated by the user. - */ - void onAnimatePosition(float scale, boolean fromUser) { - } - - /** - * Sets the progress value without going through the entire refresh process. - * - * @see #setProgress(int, boolean) - * @param progress The new progress, between 0 and {@link #getMax()} - */ - void setProgressValueOnly(int progress) { - mProgress = progress; - onProgressRefresh(getScale(progress), true); - } - - void setAnimationPosition(float position) { - mAnimationPosition = position; - refreshProgress(R.id.progress, position, true, true); - } - - float getAnimationPosition() { - return mAnimationPosition; - } - void onProgressRefresh(float scale, boolean fromUser) { if (AccessibilityManager.getInstance(mContext).isEnabled()) { scheduleAccessibilityEventSender(); } } - private synchronized void refreshProgress(int id, float progress, boolean fromUser) { - refreshProgress(id, progress, fromUser, false); - } - - private synchronized void refreshProgress(int id, float progress, boolean fromUser, - boolean animate) { + private synchronized void refreshProgress(int id, int progress, boolean fromUser) { if (mUiThreadId == Thread.currentThread().getId()) { - doRefreshProgress(id, progress, fromUser, true, animate); + doRefreshProgress(id, progress, fromUser, true); } else { if (mRefreshProgressRunnable == null) { mRefreshProgressRunnable = new RefreshProgressRunnable(); } - final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate); + final RefreshData rd = RefreshData.obtain(id, progress, fromUser); mRefreshData.add(rd); if (mAttached && !mRefreshIsPosted) { post(mRefreshProgressRunnable); @@ -1338,7 +1285,7 @@ public class ProgressBar extends View { } } } - + /** * <p>Set the current progress to the specified value. Does not do anything * if the progress bar is in indeterminate mode.</p> @@ -1348,13 +1295,13 @@ public class ProgressBar extends View { * @see #setIndeterminate(boolean) * @see #isIndeterminate() * @see #getProgress() - * @see #incrementProgressBy(int) + * @see #incrementProgressBy(int) */ @android.view.RemotableViewMethod public synchronized void setProgress(int progress) { setProgress(progress, false); } - + @android.view.RemotableViewMethod synchronized void setProgress(int progress, boolean fromUser) { if (mIndeterminate) { @@ -1380,7 +1327,7 @@ public class ProgressBar extends View { * Set the current secondary progress to the specified value. Does not do * anything if the progress bar is in indeterminate mode. * </p> - * + * * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} * @see #setIndeterminate(boolean) * @see #isIndeterminate() @@ -1461,8 +1408,8 @@ public class ProgressBar extends View { * @param max the upper range of this progress bar * * @see #getMax() - * @see #setProgress(int) - * @see #setSecondaryProgress(int) + * @see #setProgress(int) + * @see #setSecondaryProgress(int) */ @android.view.RemotableViewMethod public synchronized void setMax(int max) { @@ -1479,13 +1426,13 @@ public class ProgressBar extends View { refreshProgress(R.id.progress, mProgress, false); } } - + /** * <p>Increase the progress bar's progress by the specified amount.</p> * * @param diff the amount by which the progress must be increased * - * @see #setProgress(int) + * @see #setProgress(int) */ public synchronized final void incrementProgressBy(int diff) { setProgress(mProgress + diff); @@ -1496,7 +1443,7 @@ public class ProgressBar extends View { * * @param diff the amount by which the secondary progress must be increased * - * @see #setSecondaryProgress(int) + * @see #setSecondaryProgress(int) */ public synchronized final void incrementSecondaryProgressBy(int diff) { setSecondaryProgress(mSecondaryProgress + diff); @@ -1519,13 +1466,13 @@ public class ProgressBar extends View { if (mInterpolator == null) { mInterpolator = new LinearInterpolator(); } - + if (mTransformation == null) { mTransformation = new Transformation(); } else { mTransformation.clear(); } - + if (mAnimation == null) { mAnimation = new AlphaAnimation(0.0f, 1.0f); } else { @@ -1676,7 +1623,7 @@ public class ProgressBar extends View { } mIndeterminateDrawable.setBounds(left, top, right, bottom); } - + if (mProgressDrawable != null) { mProgressDrawable.setBounds(0, 0, right, bottom); } @@ -1746,20 +1693,20 @@ public class ProgressBar extends View { setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), resolveSizeAndState(dh, heightMeasureSpec, 0)); } - + @Override protected void drawableStateChanged() { super.drawableStateChanged(); updateDrawableState(); } - + private void updateDrawableState() { int[] state = getDrawableState(); - + if (mProgressDrawable != null && mProgressDrawable.isStateful()) { mProgressDrawable.setState(state); } - + if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { mIndeterminateDrawable.setState(state); } @@ -1781,14 +1728,14 @@ public class ProgressBar extends View { static class SavedState extends BaseSavedState { int progress; int secondaryProgress; - + /** * Constructor called from {@link ProgressBar#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } - + /** * Constructor called from {@link #CREATOR} */ @@ -1822,10 +1769,10 @@ public class ProgressBar extends View { // Force our ancestor class to save its state Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); - + ss.progress = mProgress; ss.secondaryProgress = mSecondaryProgress; - + return ss; } @@ -1833,7 +1780,7 @@ public class ProgressBar extends View { public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); - + setProgress(ss.progress); setSecondaryProgress(ss.secondaryProgress); } @@ -1849,7 +1796,7 @@ public class ProgressBar extends View { final int count = mRefreshData.size(); for (int i = 0; i < count; i++) { final RefreshData rd = mRefreshData.get(i); - doRefreshProgress(rd.id, rd.progress, rd.fromUser, rd.animate); + doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); rd.recycle(); } mRefreshData.clear(); diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java index 24fc2bb..8b01dde 100644 --- a/core/java/android/widget/RadialTimePickerView.java +++ b/core/java/android/widget/RadialTimePickerView.java @@ -452,7 +452,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { } public void initialize(int hour, int minute, boolean is24HourMode) { - mIs24HourMode = is24HourMode; + if (mIs24HourMode != is24HourMode) { + mIs24HourMode = is24HourMode; + initData(); + } setCurrentHourInternal(hour, false, false); setCurrentMinuteInternal(minute, false); diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index c4a7c0a..82b490e 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -314,10 +314,6 @@ public class RatingBar extends AbsSeekBar { dispatchRatingChange(true); } - @Override - void animateSetProgress(int progress) { - } - void dispatchRatingChange(boolean fromUser) { if (mOnRatingBarChangeListener != null) { mOnRatingBarChangeListener.onRatingChanged(this, getRating(), diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index dfdf606..4ee6418 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -45,7 +45,6 @@ import android.text.TextWatcher; import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; -import android.util.TypedValue; import android.view.CollapsibleActionView; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -99,17 +98,21 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { */ private static final String IME_OPTION_NO_MICROPHONE = "nm"; - private final SearchAutoComplete mQueryTextView; + private final SearchAutoComplete mSearchSrcTextView; private final View mSearchEditFrame; private final View mSearchPlate; private final View mSubmitArea; private final ImageView mSearchButton; - private final ImageView mSubmitButton; + private final ImageView mGoButton; private final ImageView mCloseButton; private final ImageView mVoiceButton; - private final ImageView mSearchHintIcon; private final View mDropDownAnchor; - private final int mSearchIconResId; + + /** Icon optionally displayed when the SearchView is collapsed. */ + private final ImageView mCollapsedIcon; + + /** Drawable used as an EditText hint. */ + private final Drawable mSearchHintIcon; // Resources used by SuggestionsAdapter to display suggestions. private final int mSuggestionRowLayout; @@ -262,30 +265,38 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { attrs, R.styleable.SearchView, defStyleAttr, defStyleRes); final LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE); - final int layoutResId = a.getResourceId(R.styleable.SearchView_layout, R.layout.search_view); + final int layoutResId = a.getResourceId( + R.styleable.SearchView_layout, R.layout.search_view); inflater.inflate(layoutResId, this, true); - mQueryTextView = (SearchAutoComplete) findViewById(R.id.search_src_text); - mQueryTextView.setSearchView(this); + mSearchSrcTextView = (SearchAutoComplete) findViewById(R.id.search_src_text); + mSearchSrcTextView.setSearchView(this); mSearchEditFrame = findViewById(R.id.search_edit_frame); mSearchPlate = findViewById(R.id.search_plate); mSubmitArea = findViewById(R.id.submit_area); mSearchButton = (ImageView) findViewById(R.id.search_button); - mSubmitButton = (ImageView) findViewById(R.id.search_go_btn); + mGoButton = (ImageView) findViewById(R.id.search_go_btn); mCloseButton = (ImageView) findViewById(R.id.search_close_btn); mVoiceButton = (ImageView) findViewById(R.id.search_voice_btn); - mSearchHintIcon = (ImageView) findViewById(R.id.search_mag_icon); + mCollapsedIcon = (ImageView) findViewById(R.id.search_mag_icon); // Set up icons and backgrounds. mSearchPlate.setBackground(a.getDrawable(R.styleable.SearchView_queryBackground)); mSubmitArea.setBackground(a.getDrawable(R.styleable.SearchView_submitBackground)); - mSearchIconResId = a.getResourceId(R.styleable.SearchView_searchIcon, 0); - mSearchButton.setImageResource(mSearchIconResId); - mSubmitButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon)); + mSearchButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon)); + mGoButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon)); mCloseButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_closeIcon)); mVoiceButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_voiceIcon)); - mSearchHintIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon)); + mCollapsedIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon)); + + // Prior to L MR1, the search hint icon defaulted to searchIcon. If the + // style does not have an explicit value set, fall back to that. + if (a.hasValueOrEmpty(R.styleable.SearchView_searchHintIcon)) { + mSearchHintIcon = a.getDrawable(R.styleable.SearchView_searchHintIcon); + } else { + mSearchHintIcon = a.getDrawable(R.styleable.SearchView_searchIcon); + } // Extract dropdown layout resource IDs for later use. mSuggestionRowLayout = a.getResourceId(R.styleable.SearchView_suggestionRowLayout, @@ -294,18 +305,18 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { mSearchButton.setOnClickListener(mOnClickListener); mCloseButton.setOnClickListener(mOnClickListener); - mSubmitButton.setOnClickListener(mOnClickListener); + mGoButton.setOnClickListener(mOnClickListener); mVoiceButton.setOnClickListener(mOnClickListener); - mQueryTextView.setOnClickListener(mOnClickListener); + mSearchSrcTextView.setOnClickListener(mOnClickListener); - mQueryTextView.addTextChangedListener(mTextWatcher); - mQueryTextView.setOnEditorActionListener(mOnEditorActionListener); - mQueryTextView.setOnItemClickListener(mOnItemClickListener); - mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener); - mQueryTextView.setOnKeyListener(mTextKeyListener); + mSearchSrcTextView.addTextChangedListener(mTextWatcher); + mSearchSrcTextView.setOnEditorActionListener(mOnEditorActionListener); + mSearchSrcTextView.setOnItemClickListener(mOnItemClickListener); + mSearchSrcTextView.setOnItemSelectedListener(mOnItemSelectedListener); + mSearchSrcTextView.setOnKeyListener(mTextKeyListener); // Inform any listener of focus changes - mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() { + mSearchSrcTextView.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { if (mOnQueryTextFocusChangeListener != null) { @@ -350,7 +361,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor()); + mDropDownAnchor = findViewById(mSearchSrcTextView.getDropDownAnchor()); if (mDropDownAnchor != null) { mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override @@ -393,7 +404,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { if (mVoiceButtonEnabled) { // Disable the microphone on the keyboard, as a mic is displayed near the text box // TODO: use imeOptions to disable voice input when the new API will be available - mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE); + mSearchSrcTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE); } updateViewsVisibility(isIconified()); } @@ -416,7 +427,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * @attr ref android.R.styleable#SearchView_imeOptions */ public void setImeOptions(int imeOptions) { - mQueryTextView.setImeOptions(imeOptions); + mSearchSrcTextView.setImeOptions(imeOptions); } /** @@ -427,7 +438,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * @attr ref android.R.styleable#SearchView_imeOptions */ public int getImeOptions() { - return mQueryTextView.getImeOptions(); + return mSearchSrcTextView.getImeOptions(); } /** @@ -439,7 +450,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * @attr ref android.R.styleable#SearchView_inputType */ public void setInputType(int inputType) { - mQueryTextView.setInputType(inputType); + mSearchSrcTextView.setInputType(inputType); } /** @@ -449,7 +460,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * @attr ref android.R.styleable#SearchView_inputType */ public int getInputType() { - return mQueryTextView.getInputType(); + return mSearchSrcTextView.getInputType(); } /** @hide */ @@ -461,7 +472,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { if (!isFocusable()) return false; // If it is not iconified, then give the focus to the text field if (!isIconified()) { - boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect); + boolean result = mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect); if (result) { updateViewsVisibility(false); } @@ -477,7 +488,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { mClearingFocus = true; setImeVisibility(false); super.clearFocus(); - mQueryTextView.clearFocus(); + mSearchSrcTextView.clearFocus(); mClearingFocus = false; } @@ -536,7 +547,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * @return the query string */ public CharSequence getQuery() { - return mQueryTextView.getText(); + return mSearchSrcTextView.getText(); } /** @@ -548,9 +559,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * text field. */ public void setQuery(CharSequence query, boolean submit) { - mQueryTextView.setText(query); + mSearchSrcTextView.setText(query); if (query != null) { - mQueryTextView.setSelection(mQueryTextView.length()); + mSearchSrcTextView.setSelection(mSearchSrcTextView.length()); mUserQuery = query; } @@ -711,7 +722,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { public void setSuggestionsAdapter(CursorAdapter adapter) { mSuggestionsAdapter = adapter; - mQueryTextView.setAdapter(mSuggestionsAdapter); + mSearchSrcTextView.setAdapter(mSuggestionsAdapter); } /** @@ -789,12 +800,12 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { // Visibility of views that are visible when collapsed final int visCollapsed = collapsed ? VISIBLE : GONE; // Is there text in the query - final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText()); + final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText()); mSearchButton.setVisibility(visCollapsed); updateSubmitButton(hasText); mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE); - mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE); + mCollapsedIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE); updateCloseButton(); updateVoiceButton(!hasText); updateSubmitArea(); @@ -827,13 +838,13 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { && (hasText || !mVoiceButtonEnabled)) { visibility = VISIBLE; } - mSubmitButton.setVisibility(visibility); + mGoButton.setVisibility(visibility); } private void updateSubmitArea() { int visibility = GONE; if (isSubmitAreaEnabled() - && (mSubmitButton.getVisibility() == VISIBLE + && (mGoButton.getVisibility() == VISIBLE || mVoiceButton.getVisibility() == VISIBLE)) { visibility = VISIBLE; } @@ -841,12 +852,15 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } private void updateCloseButton() { - final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText()); + final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText()); // Should we show the close button? It is not shown if there's no focus, // field is not iconified by default and there is no text in it. final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView); mCloseButton.setVisibility(showClose ? VISIBLE : GONE); - mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET); + final Drawable closeButtonImg = mCloseButton.getDrawable(); + if (closeButtonImg != null){ + closeButtonImg.setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET); + } } private void postUpdateFocusedState() { @@ -854,9 +868,16 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } private void updateFocusedState() { - boolean focused = mQueryTextView.hasFocus(); - mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET); - mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET); + final boolean focused = mSearchSrcTextView.hasFocus(); + final int[] stateSet = focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET; + final Drawable searchPlateBg = mSearchPlate.getBackground(); + if (searchPlateBg != null) { + searchPlateBg.setState(stateSet); + } + final Drawable submitAreaBg = mSubmitArea.getBackground(); + if (submitAreaBg != null) { + submitAreaBg.setState(stateSet); + } invalidate(); } @@ -896,11 +917,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { onSearchClicked(); } else if (v == mCloseButton) { onCloseClicked(); - } else if (v == mSubmitButton) { + } else if (v == mGoButton) { onSubmitQuery(); } else if (v == mVoiceButton) { onVoiceClicked(); - } else if (v == mQueryTextView) { + } else if (v == mSearchSrcTextView) { forceSuggestionQuery(); } } @@ -925,7 +946,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { // entered query with the action key SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) { - launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText() + launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mSearchSrcTextView.getText() .toString()); return true; } @@ -947,25 +968,25 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { if (DBG) { Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: " - + mQueryTextView.getListSelection()); + + mSearchSrcTextView.getListSelection()); } // If a suggestion is selected, handle enter, search key, and action keys // as presses on the selected suggestion - if (mQueryTextView.isPopupShowing() - && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) { + if (mSearchSrcTextView.isPopupShowing() + && mSearchSrcTextView.getListSelection() != ListView.INVALID_POSITION) { return onSuggestionsKey(v, keyCode, event); } // If there is text in the query box, handle enter, and action keys // The search key is handled by the dialog's onKeyDown(). - if (!mQueryTextView.isEmpty() && event.hasNoModifiers()) { + if (!mSearchSrcTextView.isEmpty() && event.hasNoModifiers()) { if (event.getAction() == KeyEvent.ACTION_UP) { if (keyCode == KeyEvent.KEYCODE_ENTER) { v.cancelLongPress(); // Launch as a regular search. - launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText() + launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mSearchSrcTextView.getText() .toString()); return true; } @@ -973,7 +994,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { if (event.getAction() == KeyEvent.ACTION_DOWN) { SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) { - launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView + launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mSearchSrcTextView .getText().toString()); return true; } @@ -1001,7 +1022,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { // "click") if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_TAB) { - int position = mQueryTextView.getListSelection(); + int position = mSearchSrcTextView.getListSelection(); return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null); } @@ -1012,18 +1033,18 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { // left key, at end if right key // TODO: Reverse left/right for right-to-left languages, e.g. // Arabic - int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView + int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mSearchSrcTextView .length(); - mQueryTextView.setSelection(selPoint); - mQueryTextView.setListSelection(0); - mQueryTextView.clearListSelection(); - mQueryTextView.ensureImeVisible(true); + mSearchSrcTextView.setSelection(selPoint); + mSearchSrcTextView.setListSelection(0); + mSearchSrcTextView.clearListSelection(); + mSearchSrcTextView.ensureImeVisible(true); return true; } // Next, check for an "up and out" move - if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) { + if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchSrcTextView.getListSelection()) { // TODO: restoreUserQuery(); // let ACTV complete the move return false; @@ -1035,7 +1056,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { && ((actionKey.getSuggestActionMsg() != null) || (actionKey .getSuggestActionMsgColumn() != null))) { // launch suggestion using action key column - int position = mQueryTextView.getListSelection(); + int position = mSearchSrcTextView.getListSelection(); if (position != ListView.INVALID_POSITION) { Cursor c = mSuggestionsAdapter.getCursor(); if (c.moveToPosition(position)) { @@ -1078,24 +1099,24 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } private CharSequence getDecoratedHint(CharSequence hintText) { - // If the field is always expanded, then don't add the search icon to the hint - if (!mIconifiedByDefault) { + // If the field is always expanded or we don't have a search hint icon, + // then don't add the search icon to the hint. + if (!mIconifiedByDefault || mSearchHintIcon == null) { return hintText; } - final Drawable searchIcon = getContext().getDrawable(mSearchIconResId); - final int textSize = (int) (mQueryTextView.getTextSize() * 1.25); - searchIcon.setBounds(0, 0, textSize, textSize); + final int textSize = (int) (mSearchSrcTextView.getTextSize() * 1.25); + mSearchHintIcon.setBounds(0, 0, textSize, textSize); - final SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon + final SpannableStringBuilder ssb = new SpannableStringBuilder(" "); + ssb.setSpan(new ImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append(hintText); - ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return ssb; } private void updateQueryHint() { if (mQueryHint != null) { - mQueryTextView.setHint(getDecoratedHint(mQueryHint)); + mSearchSrcTextView.setHint(getDecoratedHint(mQueryHint)); } else if (mSearchable != null) { CharSequence hint = null; int hintId = mSearchable.getHintId(); @@ -1103,10 +1124,10 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { hint = getContext().getString(hintId); } if (hint != null) { - mQueryTextView.setHint(getDecoratedHint(hint)); + mSearchSrcTextView.setHint(getDecoratedHint(hint)); } } else { - mQueryTextView.setHint(getDecoratedHint("")); + mSearchSrcTextView.setHint(getDecoratedHint("")); } } @@ -1114,9 +1135,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * Updates the auto-complete text view. */ private void updateSearchAutoComplete() { - mQueryTextView.setDropDownAnimationStyle(0); // no animation - mQueryTextView.setThreshold(mSearchable.getSuggestThreshold()); - mQueryTextView.setImeOptions(mSearchable.getImeOptions()); + mSearchSrcTextView.setDropDownAnimationStyle(0); // no animation + mSearchSrcTextView.setThreshold(mSearchable.getSuggestThreshold()); + mSearchSrcTextView.setImeOptions(mSearchable.getImeOptions()); int inputType = mSearchable.getInputType(); // We only touch this if the input type is set up for text (which it almost certainly // should be, in the case of search!) @@ -1135,7 +1156,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; } } - mQueryTextView.setInputType(inputType); + mSearchSrcTextView.setInputType(inputType); if (mSuggestionsAdapter != null) { mSuggestionsAdapter.changeCursor(null); } @@ -1144,7 +1165,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { if (mSearchable.getSuggestAuthority() != null) { mSuggestionsAdapter = new SuggestionsAdapter(getContext(), this, mSearchable, mOutsideDrawablesCache); - mQueryTextView.setAdapter(mSuggestionsAdapter); + mSearchSrcTextView.setAdapter(mSuggestionsAdapter); ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement( mQueryRefinement ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY); @@ -1161,7 +1182,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { int visibility = GONE; if (mVoiceButtonEnabled && !isIconified() && empty) { visibility = VISIBLE; - mSubmitButton.setVisibility(GONE); + mGoButton.setVisibility(GONE); } mVoiceButton.setVisibility(visibility); } @@ -1178,7 +1199,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { }; private void onTextChanged(CharSequence newText) { - CharSequence text = mQueryTextView.getText(); + CharSequence text = mSearchSrcTextView.getText(); mUserQuery = text; boolean hasText = !TextUtils.isEmpty(text); updateSubmitButton(hasText); @@ -1192,7 +1213,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } private void onSubmitQuery() { - CharSequence query = mQueryTextView.getText(); + CharSequence query = mSearchSrcTextView.getText(); if (query != null && TextUtils.getTrimmedLength(query) > 0) { if (mOnQueryChangeListener == null || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) { @@ -1206,11 +1227,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } private void dismissSuggestions() { - mQueryTextView.dismissDropDown(); + mSearchSrcTextView.dismissDropDown(); } private void onCloseClicked() { - CharSequence text = mQueryTextView.getText(); + CharSequence text = mSearchSrcTextView.getText(); if (TextUtils.isEmpty(text)) { if (mIconifiedByDefault) { // If the app doesn't override the close behavior @@ -1222,8 +1243,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } } } else { - mQueryTextView.setText(""); - mQueryTextView.requestFocus(); + mSearchSrcTextView.setText(""); + mSearchSrcTextView.requestFocus(); setImeVisibility(true); } @@ -1231,7 +1252,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { private void onSearchClicked() { updateViewsVisibility(false); - mQueryTextView.requestFocus(); + mSearchSrcTextView.requestFocus(); setImeVisibility(true); if (mOnSearchClickListener != null) { mOnSearchClickListener.onClick(this); @@ -1266,7 +1287,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { // Delayed update to make sure that the focus has settled down and window focus changes // don't affect it. A synchronous update was not working. postUpdateFocusedState(); - if (mQueryTextView.hasFocus()) { + if (mSearchSrcTextView.hasFocus()) { forceSuggestionQuery(); } } @@ -1286,7 +1307,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { setQuery("", false); clearFocus(); updateViewsVisibility(true); - mQueryTextView.setImeOptions(mCollapsedImeOptions); + mSearchSrcTextView.setImeOptions(mCollapsedImeOptions); mExpandedInActionView = false; } @@ -1298,9 +1319,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { if (mExpandedInActionView) return; mExpandedInActionView = true; - mCollapsedImeOptions = mQueryTextView.getImeOptions(); - mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN); - mQueryTextView.setText(""); + mCollapsedImeOptions = mSearchSrcTextView.getImeOptions(); + mSearchSrcTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN); + mSearchSrcTextView.setText(""); setIconified(false); } @@ -1326,17 +1347,17 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { ? res.getDimensionPixelSize(R.dimen.dropdownitem_icon_width) + res.getDimensionPixelSize(R.dimen.dropdownitem_text_padding_left) : 0; - mQueryTextView.getDropDownBackground().getPadding(dropDownPadding); + mSearchSrcTextView.getDropDownBackground().getPadding(dropDownPadding); int offset; if (isLayoutRtl) { offset = - dropDownPadding.left; } else { offset = anchorPadding - (dropDownPadding.left + iconOffset); } - mQueryTextView.setDropDownHorizontalOffset(offset); + mSearchSrcTextView.setDropDownHorizontalOffset(offset); final int width = mDropDownAnchor.getWidth() + dropDownPadding.left + dropDownPadding.right + iconOffset - anchorPadding; - mQueryTextView.setDropDownWidth(width); + mSearchSrcTextView.setDropDownWidth(width); } } @@ -1394,7 +1415,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * Query rewriting. */ private void rewriteQueryFromSuggestion(int position) { - CharSequence oldQuery = mQueryTextView.getText(); + CharSequence oldQuery = mSearchSrcTextView.getText(); Cursor c = mSuggestionsAdapter.getCursor(); if (c == null) { return; @@ -1460,9 +1481,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { * Sets the text in the query box, without updating the suggestions. */ private void setQuery(CharSequence query) { - mQueryTextView.setText(query, true); + mSearchSrcTextView.setText(query, true); // Move the cursor to the end - mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length()); + mSearchSrcTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length()); } private void launchQuerySearch(int actionKey, String actionMsg, String query) { @@ -1648,8 +1669,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } private void forceSuggestionQuery() { - mQueryTextView.doBeforeTextChanged(); - mQueryTextView.doAfterTextChanged(); + mSearchSrcTextView.doBeforeTextChanged(); + mSearchSrcTextView.doAfterTextChanged(); } static boolean isLandscapeMode(Context context) { diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java index ecd2912..24ebb2c 100644 --- a/core/java/android/widget/SimpleMonthAdapter.java +++ b/core/java/android/widget/SimpleMonthAdapter.java @@ -16,8 +16,12 @@ package android.widget; +import com.android.internal.R; + import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.Color; import android.view.View; import android.view.ViewGroup; import android.widget.SimpleMonthView.OnDayClickListener; @@ -33,15 +37,14 @@ class SimpleMonthAdapter extends BaseAdapter { private final Context mContext; - private Calendar mSelectedDay; - private ColorStateList mCalendarTextColors; + private Calendar mSelectedDay = Calendar.getInstance(); + private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK); private OnDaySelectedListener mOnDaySelectedListener; private int mFirstDayOfWeek; public SimpleMonthAdapter(Context context) { mContext = context; - mSelectedDay = Calendar.getInstance(); } public void setRange(Calendar min, Calendar max) { @@ -57,6 +60,10 @@ class SimpleMonthAdapter extends BaseAdapter { notifyDataSetInvalidated(); } + public int getFirstDayOfWeek() { + return mFirstDayOfWeek; + } + /** * Updates the selected day and related parameters. * @@ -81,6 +88,24 @@ class SimpleMonthAdapter extends BaseAdapter { mCalendarTextColors = colors; } + /** + * Sets the text color, size, style, hint color, and highlight color from + * the specified TextAppearance resource. This is mostly copied from + * {@link TextView#setTextAppearance(Context, int)}. + */ + void setCalendarTextAppearance(int resId) { + final TypedArray a = mContext.obtainStyledAttributes(resId, R.styleable.TextAppearance); + + final ColorStateList textColor = a.getColorStateList(R.styleable.TextAppearance_textColor); + if (textColor != null) { + mCalendarTextColors = textColor; + } + + // TODO: Support font size, etc. + + a.recycle(); + } + @Override public int getCount() { final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR); diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index 1a86809..e2acaac 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -16,6 +16,7 @@ package android.widget; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -26,6 +27,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.text.format.DateFormat; import android.util.AttributeSet; @@ -127,6 +129,8 @@ public class TextClock extends TextView { private Calendar mTime; private String mTimeZone; + private boolean mShowCurrentUserTime; + private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { @@ -342,6 +346,22 @@ public class TextClock extends TextView { } /** + * Sets whether this clock should always track the current user and not the user of the + * current process. This is used for single instance processes like the systemUI who need + * to display time for different users. + * + * @hide + */ + public void setShowCurrentUserTime(boolean showCurrentUserTime) { + mShowCurrentUserTime = showCurrentUserTime; + + chooseFormat(); + onTimeChanged(); + unregisterObserver(); + registerObserver(); + } + + /** * Indicates whether the system is currently using the 24-hour mode. * * When the system is in 24-hour mode, this view will use the pattern @@ -360,7 +380,11 @@ public class TextClock extends TextView { * @see #getFormat24Hour() */ public boolean is24HourModeEnabled() { - return DateFormat.is24HourFormat(getContext()); + if (mShowCurrentUserTime) { + return DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser()); + } else { + return DateFormat.is24HourFormat(getContext()); + } } /** @@ -500,7 +524,13 @@ public class TextClock extends TextView { private void registerObserver() { final ContentResolver resolver = getContext().getContentResolver(); - resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); + if (mShowCurrentUserTime) { + resolver.registerContentObserver(Settings.System.CONTENT_URI, true, + mFormatChangeObserver, UserHandle.USER_ALL); + } else { + resolver.registerContentObserver(Settings.System.CONTENT_URI, true, + mFormatChangeObserver); + } } private void unregisterReceiver() { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 44ccde0..7d4a2fb 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -42,6 +42,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.text.BoringLayout; import android.text.DynamicLayout; @@ -286,9 +287,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mCurTextColor; private int mCurHintTextColor; private boolean mFreezesText; - private boolean mTemporaryDetach; private boolean mDispatchTemporaryDetach; + /** Whether this view is temporarily detached from the parent view. */ + boolean mTemporaryDetach; + private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); @@ -3631,9 +3634,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (mHintTextColor != null) { color = mHintTextColor.getColorForState(getDrawableState(), 0); - if (color != mCurHintTextColor && mText.length() == 0) { + if (color != mCurHintTextColor) { mCurHintTextColor = color; - inval = true; + if (mText.length() == 0) { + inval = true; + } } } if (inval) { @@ -3809,7 +3814,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Display the error later, after the first layout pass post(new Runnable() { public void run() { - setError(error); + if (mEditor == null || !mEditor.mErrorWasChanged) { + setError(error); + } } }); } @@ -5367,9 +5374,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; final int maxScrollY = mLayout.getHeight() - vspace; + // Add sufficient space for cursor and tone marks + int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors + int fudgedPaddingRight = Math.max(0, compoundPaddingRight - (cursorWidth - 1)); + float clipLeft = compoundPaddingLeft + scrollX; float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; - float clipRight = right - left - compoundPaddingRight + scrollX; + float clipRight = right - left - fudgedPaddingRight + scrollX; float clipBottom = bottom - top + scrollY - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); @@ -8381,8 +8392,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * to speak passwords. */ private boolean shouldSpeakPasswordsForAccessibility() { - return (Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1); + return (Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0, UserHandle.USER_CURRENT) == 1); } @Override diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index 1b25486..70fb510 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -1079,7 +1079,9 @@ public final class ProcessStats implements Parcelable { ProcessDataCollection totals = new ProcessDataCollection(screenStates, memStates, procStates); computeProcessData(proc, totals, now); - if (totals.totalTime != 0 || totals.numPss != 0) { + double percentage = (double) totals.totalTime / (double) totalTime * 100; + // We don't print percentages < .01, so just drop those. + if (percentage >= 0.005 || totals.numPss != 0) { if (prefix != null) { pw.print(prefix); } @@ -2470,7 +2472,7 @@ public final class ProcessStats implements Parcelable { totalMem.totalTime, totalPss, totalMem.sysMemSamples); totalPss = printMemoryCategory(pw, " ", "Free ", totalMem.sysMemFreeWeight, totalMem.totalTime, totalPss, totalMem.sysMemSamples); - totalPss = printMemoryCategory(pw, " ", "Z-Ram ", totalMem.sysMemZRamWeight, + totalPss = printMemoryCategory(pw, " ", "Z-Ram ", totalMem.sysMemZRamWeight, totalMem.totalTime, totalPss, totalMem.sysMemSamples); pw.print(" TOTAL : "); printSizeValue(pw, totalPss); diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 376db6e..2db466a 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -840,7 +840,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } if (N > 1) { Comparator<ResolveInfo> rComparator = - new ResolverComparator(ResolverActivity.this); + new ResolverComparator(ResolverActivity.this, mIntent); Collections.sort(currentResolveList, rComparator); } // First put the initial items at the top. @@ -1093,11 +1093,20 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + static final boolean isSpecificUriMatch(int match) { + match = match&IntentFilter.MATCH_CATEGORY_MASK; + return match >= IntentFilter.MATCH_CATEGORY_HOST + && match <= IntentFilter.MATCH_CATEGORY_PATH; + } + class ResolverComparator implements Comparator<ResolveInfo> { private final Collator mCollator; + private final boolean mHttp; - public ResolverComparator(Context context) { + public ResolverComparator(Context context, Intent intent) { mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); + String scheme = intent.getScheme(); + mHttp = "http".equals(scheme) || "https".equals(scheme); } @Override @@ -1107,6 +1116,17 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return 1; } + if (mHttp) { + // Special case: we want filters that match URI paths/schemes to be + // ordered before others. This is for the case when opening URIs, + // to make native apps go above browsers. + final boolean lhsSpecific = isSpecificUriMatch(lhs.match); + final boolean rhsSpecific = isSpecificUriMatch(rhs.match); + if (lhsSpecific != rhsSpecific) { + return lhsSpecific ? -1 : 1; + } + } + if (mStats != null) { final long timeDiff = getPackageTimeSpent(rhs.activityInfo.packageName) - diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index d8dffe0..044383e 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -35,6 +35,8 @@ import android.util.Log; import com.android.org.bouncycastle.util.encoders.Base64; +import libcore.io.IoUtils; + import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; @@ -559,11 +561,7 @@ public class LocalTransport extends BackupTransport { // Full restore handling private void resetFullRestoreState() { - try { - mCurFullRestoreStream.close(); - } catch (IOException e) { - Log.w(TAG, "Unable to close full restore input stream"); - } + IoUtils.closeQuietly(mCurFullRestoreStream); mCurFullRestoreStream = null; mFullRestoreSocketStream = null; mFullRestoreBuffer = null; diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 3d016be..c5d9db4 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -25,6 +25,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.net.LinkAddress; +import android.net.Network; import android.net.RouteInfo; import android.os.Parcel; import android.os.Parcelable; @@ -99,6 +100,7 @@ public class VpnConfig implements Parcelable { public boolean allowBypass; public boolean allowIPv4; public boolean allowIPv6; + public Network[] underlyingNetworks; public void updateAllowedFamilies(InetAddress address) { if (address instanceof Inet4Address) { @@ -162,6 +164,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowBypass ? 1 : 0); out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); + out.writeTypedArray(underlyingNetworks, flags); } public static final Parcelable.Creator<VpnConfig> CREATOR = @@ -186,6 +189,7 @@ public class VpnConfig implements Parcelable { config.allowBypass = in.readInt() != 0; config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; + config.underlyingNetworks = in.createTypedArray(Network.CREATOR); return config; } diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index 17685fd..99286cb 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -49,6 +49,10 @@ public class HandlerCaller { mCallback = callback; } + public Handler getHandler() { + return mH; + } + public void executeOrSendMessage(Message msg) { // If we are calling this from the main thread, then we can call // right through. Otherwise, we need to send the message to the diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 46850da..62088fa 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -45,6 +45,8 @@ import libcore.io.IoUtils; import java.io.BufferedReader; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -91,9 +93,9 @@ public class ZygoteInit { private static Resources mResources; /** - * The name of a resource file that contains classes to preload. + * The path of a file that contains classes to preload. */ - private static final String PRELOADED_CLASSES = "preloaded-classes"; + private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes"; /** Controls whether we should preload resources during zygote init. */ private static final boolean PRELOAD_RESOURCES = true; @@ -278,74 +280,76 @@ public class ZygoteInit { private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); - InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream( - PRELOADED_CLASSES); - if (is == null) { + InputStream is; + try { + is = new FileInputStream(PRELOADED_CLASSES); + } catch (FileNotFoundException e) { Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + "."); - } else { - Log.i(TAG, "Preloading classes..."); - long startTime = SystemClock.uptimeMillis(); + return; + } - // Drop root perms while running static initializers. - setEffectiveGroup(UNPRIVILEGED_GID); - setEffectiveUser(UNPRIVILEGED_UID); + Log.i(TAG, "Preloading classes..."); + long startTime = SystemClock.uptimeMillis(); - // Alter the target heap utilization. With explicit GCs this - // is not likely to have any effect. - float defaultUtilization = runtime.getTargetHeapUtilization(); - runtime.setTargetHeapUtilization(0.8f); + // Drop root perms while running static initializers. + setEffectiveGroup(UNPRIVILEGED_GID); + setEffectiveUser(UNPRIVILEGED_UID); - try { - BufferedReader br - = new BufferedReader(new InputStreamReader(is), 256); - - int count = 0; - String line; - while ((line = br.readLine()) != null) { - // Skip comments and blank lines. - line = line.trim(); - if (line.startsWith("#") || line.equals("")) { - continue; - } + // Alter the target heap utilization. With explicit GCs this + // is not likely to have any effect. + float defaultUtilization = runtime.getTargetHeapUtilization(); + runtime.setTargetHeapUtilization(0.8f); - try { - if (false) { - Log.v(TAG, "Preloading " + line + "..."); - } - Class.forName(line); - count++; - } catch (ClassNotFoundException e) { - Log.w(TAG, "Class not found for preloading: " + line); - } catch (UnsatisfiedLinkError e) { - Log.w(TAG, "Problem preloading " + line + ": " + e); - } catch (Throwable t) { - Log.e(TAG, "Error preloading " + line + ".", t); - if (t instanceof Error) { - throw (Error) t; - } - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } - throw new RuntimeException(t); - } + try { + BufferedReader br + = new BufferedReader(new InputStreamReader(is), 256); + + int count = 0; + String line; + while ((line = br.readLine()) != null) { + // Skip comments and blank lines. + line = line.trim(); + if (line.startsWith("#") || line.equals("")) { + continue; } - Log.i(TAG, "...preloaded " + count + " classes in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); - } catch (IOException e) { - Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); - } finally { - IoUtils.closeQuietly(is); - // Restore default. - runtime.setTargetHeapUtilization(defaultUtilization); - - // Fill in dex caches with classes, fields, and methods brought in by preloading. - runtime.preloadDexCaches(); - - // Bring back root. We'll need it later. - setEffectiveUser(ROOT_UID); - setEffectiveGroup(ROOT_GID); + try { + if (false) { + Log.v(TAG, "Preloading " + line + "..."); + } + Class.forName(line); + count++; + } catch (ClassNotFoundException e) { + Log.w(TAG, "Class not found for preloading: " + line); + } catch (UnsatisfiedLinkError e) { + Log.w(TAG, "Problem preloading " + line + ": " + e); + } catch (Throwable t) { + Log.e(TAG, "Error preloading " + line + ".", t); + if (t instanceof Error) { + throw (Error) t; + } + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + throw new RuntimeException(t); + } } + + Log.i(TAG, "...preloaded " + count + " classes in " + + (SystemClock.uptimeMillis()-startTime) + "ms."); + } catch (IOException e) { + Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); + } finally { + IoUtils.closeQuietly(is); + // Restore default. + runtime.setTargetHeapUtilization(defaultUtilization); + + // Fill in dex caches with classes, fields, and methods brought in by preloading. + runtime.preloadDexCaches(); + + // Bring back root. We'll need it later. + setEffectiveUser(ROOT_UID); + setEffectiveGroup(ROOT_GID); } } diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index 64f3bea..f93b1a1 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -15,47 +15,34 @@ */ package com.android.internal.policy; -import android.view.MotionEvent; - import com.android.internal.policy.IKeyguardShowCallback; +import com.android.internal.policy.IKeyguardStateCallback; import com.android.internal.policy.IKeyguardExitCallback; import android.os.Bundle; -interface IKeyguardService { - boolean isShowing(); - boolean isSecure(); - boolean isShowingAndNotOccluded(); - boolean isInputRestricted(); - boolean isDismissable(); - oneway void verifyUnlock(IKeyguardExitCallback callback); - oneway void keyguardDone(boolean authenticated, boolean wakeup); - +oneway interface IKeyguardService { /** * Sets the Keyguard as occluded when a window dismisses the Keyguard with flag * FLAG_SHOW_ON_LOCK_SCREEN. * * @param isOccluded Whether the Keyguard is occluded by another window. - * @return See IKeyguardServiceConstants.KEYGUARD_SERVICE_SET_OCCLUDED_*. This is needed because - * PhoneWindowManager needs to set these flags immediately and can't wait for the - * Keyguard thread to pick it up. In the hidden case, PhoneWindowManager is solely - * responsible to make sure that the flags are unset. */ - int setOccluded(boolean isOccluded); - - oneway void dismiss(); - oneway void onDreamingStarted(); - oneway void onDreamingStopped(); - oneway void onScreenTurnedOff(int reason); - oneway void onScreenTurnedOn(IKeyguardShowCallback callback); - oneway void setKeyguardEnabled(boolean enabled); - oneway void onSystemReady(); - oneway void doKeyguardTimeout(in Bundle options); - oneway void setCurrentUser(int userId); - oneway void showAssistant(); - oneway void dispatch(in MotionEvent event); - oneway void launchCamera(); - oneway void onBootCompleted(); + void setOccluded(boolean isOccluded); + + void addStateMonitorCallback(IKeyguardStateCallback callback); + void verifyUnlock(IKeyguardExitCallback callback); + void keyguardDone(boolean authenticated, boolean wakeup); + void dismiss(); + void onDreamingStarted(); + void onDreamingStopped(); + void onScreenTurnedOff(int reason); + void onScreenTurnedOn(IKeyguardShowCallback callback); + void setKeyguardEnabled(boolean enabled); + void onSystemReady(); + void doKeyguardTimeout(in Bundle options); + void setCurrentUser(int userId); + void onBootCompleted(); /** * Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper @@ -64,11 +51,11 @@ interface IKeyguardService { * @param startTime the start time of the animation in uptime milliseconds * @param fadeoutDuration the duration of the exit animation, in milliseconds */ - oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration); + void startKeyguardExitAnimation(long startTime, long fadeoutDuration); /** * Notifies the Keyguard that the activity that was starting has now been drawn and it's safe * to start the keyguard dismiss sequence. */ - oneway void onActivityDrawn(); + void onActivityDrawn(); } diff --git a/core/java/com/android/internal/policy/IKeyguardServiceConstants.java b/core/java/com/android/internal/policy/IKeyguardServiceConstants.java deleted file mode 100644 index b88174a..0000000 --- a/core/java/com/android/internal/policy/IKeyguardServiceConstants.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.internal.policy; - -/** - * @hide - */ -public class IKeyguardServiceConstants { - - /** - * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}: - * Don't change the keyguard window flags. - */ - public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_NONE = 0; - - /** - * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}: - * Set the keyguard window flags to FLAG_SHOW_WALLPAPER and PRIVATE_FLAG_KEYGUARD. - */ - public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_SET_FLAGS = 1; - - /** - * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}: - * Unset the keyguard window flags to FLAG_SHOW_WALLPAPER and PRIVATE_FLAG_KEYGUARD. - */ - public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_UNSET_FLAGS = 2; -} diff --git a/core/java/android/net/http/Timer.java b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl index cc15a30..db3b40b 100644 --- a/core/java/android/net/http/Timer.java +++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,29 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.internal.policy; -package android.net.http; - -import android.os.SystemClock; - -/** - * {@hide} - * Debugging tool - */ -class Timer { - - private long mStart; - private long mLast; - - public Timer() { - mStart = mLast = SystemClock.uptimeMillis(); - } - - public void mark(String message) { - long now = SystemClock.uptimeMillis(); - if (HttpLog.LOGV) { - HttpLog.v(message + " " + (now - mLast) + " total " + (now - mStart)); - } - mLast = now; - } -} +interface IKeyguardStateCallback { + void onShowingStateChanged(boolean showing); + void onSimSecureStateChanged(boolean simSecure); + void onInputRestrictedStateChanged(boolean inputRestricted); +}
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 3ccced5..f6c42af 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -191,9 +191,6 @@ public class LockPatternUtils { return trust; } - /** - * @param contentResolver Used to look up and save settings. - */ public LockPatternUtils(Context context) { mContext = context; mContentResolver = context.getContentResolver(); @@ -384,8 +381,16 @@ public class LockPatternUtils { * @return Whether a saved pattern exists. */ public boolean savedPatternExists() { + return savedPatternExists(getCurrentOrCallingUserId()); + } + + /** + * Check to see if the user has stored a lock pattern. + * @return Whether a saved pattern exists. + */ + public boolean savedPatternExists(int userId) { try { - return getLockSettings().havePattern(getCurrentOrCallingUserId()); + return getLockSettings().havePattern(userId); } catch (RemoteException re) { return false; } @@ -396,8 +401,16 @@ public class LockPatternUtils { * @return Whether a saved pattern exists. */ public boolean savedPasswordExists() { + return savedPasswordExists(getCurrentOrCallingUserId()); + } + + /** + * Check to see if the user has stored a lock pattern. + * @return Whether a saved pattern exists. + */ + public boolean savedPasswordExists(int userId) { try { - return getLockSettings().havePassword(getCurrentOrCallingUserId()); + return getLockSettings().havePassword(userId); } catch (RemoteException re) { return false; } @@ -474,17 +487,23 @@ public class LockPatternUtils { return activePasswordQuality; } + public void clearLock(boolean isFallback) { + clearLock(isFallback, getCurrentOrCallingUserId()); + } + /** * Clear any lock pattern or password. */ - public void clearLock(boolean isFallback) { - if(!isFallback) deleteGallery(); - saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - setLockPatternEnabled(false); - saveLockPattern(null); - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); - setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); - onAfterChangingPassword(); + public void clearLock(boolean isFallback, int userHandle) { + if(!isFallback) deleteGallery(userHandle); + saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, isFallback, + userHandle); + setLockPatternEnabled(false, userHandle); + saveLockPattern(null, isFallback, userHandle); + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle); + setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userHandle); + onAfterChangingPassword(userHandle); } /** @@ -531,11 +550,11 @@ public class LockPatternUtils { /** * Calls back SetupFaceLock to delete the gallery file when the lock type is changed */ - void deleteGallery() { - if(usingBiometricWeak()) { + void deleteGallery(int userId) { + if(usingBiometricWeak(userId)) { Intent intent = new Intent().setAction("com.android.facelock.DELETE_GALLERY"); intent.putExtra("deleteGallery", true); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, new UserHandle(userId)); } } @@ -550,11 +569,20 @@ public class LockPatternUtils { /** * Save a lock pattern. * @param pattern The new pattern to save. - * @param isFallback Specifies if this is a fallback to biometric weak */ public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) { + this.saveLockPattern(pattern, isFallback, getCurrentOrCallingUserId()); + } + + /** + * Save a lock pattern. + * @param pattern The new pattern to save. + * @param isFallback Specifies if this is a fallback to biometric weak + * @param userId the user whose pattern is to be saved. + */ + public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback, + int userId) { try { - int userId = getCurrentOrCallingUserId(); getLockSettings().setLockPattern(patternToString(pattern), userId); DevicePolicyManager dpm = getDevicePolicyManager(); if (pattern != null) { @@ -570,17 +598,17 @@ public class LockPatternUtils { } } - setBoolean(PATTERN_EVER_CHOSEN_KEY, true); + setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId); if (!isFallback) { - deleteGallery(); - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + deleteGallery(userId); + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size(), 0, 0, 0, 0, 0, 0, userId); } else { - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, userId); setLong(PASSWORD_TYPE_ALTERNATE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - finishBiometricWeak(); + DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); + finishBiometricWeak(userId); dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, 0, 0, 0, 0, 0, 0, 0, userId); } @@ -588,7 +616,7 @@ public class LockPatternUtils { dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userId); } - onAfterChangingPassword(); + onAfterChangingPassword(userId); } catch (RemoteException re) { Log.e(TAG, "Couldn't save lock pattern " + re); } @@ -806,7 +834,7 @@ public class LockPatternUtils { } if (!isFallback) { - deleteGallery(); + deleteGallery(userHandle); setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { int letters = 0; @@ -846,7 +874,7 @@ public class LockPatternUtils { userHandle); setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality), userHandle); - finishBiometricWeak(); + finishBiometricWeak(userHandle); dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, 0, 0, 0, 0, 0, 0, 0, userHandle); } @@ -854,7 +882,7 @@ public class LockPatternUtils { // password hashes have the same length for simplicity of implementation. String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle); if (passwordHistory == null) { - passwordHistory = new String(); + passwordHistory = ""; } int passwordHistoryLength = getRequestedPasswordHistoryLength(); if (passwordHistoryLength == 0) { @@ -881,7 +909,7 @@ public class LockPatternUtils { DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle); } - onAfterChangingPassword(); + onAfterChangingPassword(userHandle); } catch (RemoteException re) { // Cant do much Log.e(TAG, "Unable to save lock password " + re); @@ -955,8 +983,15 @@ public class LockPatternUtils { * @return true if the lockscreen method is set to biometric weak */ public boolean usingBiometricWeak() { - int quality = - (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); + return usingBiometricWeak(getCurrentOrCallingUserId()); + } + + /** + * @return true if the lockscreen method is set to biometric weak + */ + public boolean usingBiometricWeak(int userId) { + int quality = (int) getLong( + PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId); return quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; } @@ -1096,15 +1131,22 @@ public class LockPatternUtils { * @return Whether the lock pattern is enabled, or if it is set as a backup for biometric weak */ public boolean isLockPatternEnabled() { + return isLockPatternEnabled(getCurrentOrCallingUserId()); + } + + /** + * @return Whether the lock pattern is enabled, or if it is set as a backup for biometric weak + */ + public boolean isLockPatternEnabled(int userId) { final boolean backupEnabled = getLong(PASSWORD_TYPE_ALTERNATE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId) == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; - return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false) - && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) - == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING || - (usingBiometricWeak() && backupEnabled)); + return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false, userId) + && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId) == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING + || (usingBiometricWeak(userId) && backupEnabled)); } /** @@ -1160,7 +1202,14 @@ public class LockPatternUtils { * Set whether the lock pattern is enabled. */ public void setLockPatternEnabled(boolean enabled) { - setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, enabled); + setLockPatternEnabled(enabled, getCurrentOrCallingUserId()); + } + + /** + * Set whether the lock pattern is enabled. + */ + public void setLockPatternEnabled(boolean enabled, int userHandle) { + setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, enabled, userHandle); } /** @@ -1485,15 +1534,20 @@ public class LockPatternUtils { } public boolean isSecure() { - long mode = getKeyguardStoredPasswordQuality(); + return isSecure(getCurrentOrCallingUserId()); + } + + public boolean isSecure(int userId) { + long mode = getKeyguardStoredPasswordQuality(userId); final boolean isPattern = mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; final boolean isPassword = mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; - final boolean secure = isPattern && isLockPatternEnabled() && savedPatternExists() - || isPassword && savedPasswordExists(); + final boolean secure = + isPattern && isLockPatternEnabled(userId) && savedPatternExists(userId) + || isPassword && savedPasswordExists(userId); return secure; } @@ -1549,15 +1603,15 @@ public class LockPatternUtils { return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); } - private void finishBiometricWeak() { - setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true); + private void finishBiometricWeak(int userId) { + setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true, userId); // Launch intent to show final screen, this also // moves the temporary gallery to the actual gallery Intent intent = new Intent(); intent.setClassName("com.android.facelock", "com.android.facelock.SetupEndScreen"); - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, new UserHandle(userId)); } public void setPowerButtonInstantlyLocks(boolean enabled) { @@ -1651,8 +1705,8 @@ public class LockPatternUtils { getTrustManager().reportRequireCredentialEntry(userId); } - private void onAfterChangingPassword() { - getTrustManager().reportEnabledTrustAgentsChanged(getCurrentOrCallingUserId()); + private void onAfterChangingPassword(int userHandle) { + getTrustManager().reportEnabledTrustAgentsChanged(userHandle); } public boolean isCredentialRequiredToDecrypt(boolean defaultValue) { diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java index 99b1bae..d617c05 100644 --- a/core/java/com/android/internal/widget/SwipeDismissLayout.java +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -26,6 +26,7 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; @@ -76,6 +77,19 @@ public class SwipeDismissLayout extends FrameLayout { private OnDismissedListener mDismissedListener; private OnSwipeProgressChangedListener mProgressListener; + private ViewTreeObserver.OnEnterAnimationCompleteListener mOnEnterAnimationCompleteListener = + new ViewTreeObserver.OnEnterAnimationCompleteListener() { + @Override + public void onEnterAnimationComplete() { + // SwipeDismissLayout assumes that the host Activity is translucent + // and temporarily disables translucency when it is fully visible. + // As soon as the user starts swiping, we will re-enable + // translucency. + if (getContext() instanceof Activity) { + ((Activity) getContext()).convertFromTranslucent(); + } + } + }; private float mLastX; @@ -103,13 +117,6 @@ public class SwipeDismissLayout extends FrameLayout { android.R.integer.config_shortAnimTime); mCancelInterpolator = new DecelerateInterpolator(1.5f); mDismissInterpolator = new AccelerateInterpolator(1.5f); - // SwipeDismissLayout assumes that the host Activity is translucent - // and temporarily disables translucency when it is fully visible. - // As soon as the user starts swiping, we will re-enable - // translucency. - if (context instanceof Activity) { - ((Activity) context).convertFromTranslucent(); - } } public void setOnDismissedListener(OnDismissedListener listener) { @@ -121,6 +128,24 @@ public class SwipeDismissLayout extends FrameLayout { } @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (getContext() instanceof Activity) { + getViewTreeObserver().addOnEnterAnimationCompleteListener( + mOnEnterAnimationCompleteListener); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (getContext() instanceof Activity) { + getViewTreeObserver().removeOnEnterAnimationCompleteListener( + mOnEnterAnimationCompleteListener); + } + } + + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // offset because the view is translated during swipe ev.offsetLocation(mTranslationX, 0); diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java index 26e2e2a..ed7ce63 100644 --- a/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/core/java/com/android/server/backup/SystemBackupAgent.java @@ -23,6 +23,7 @@ import android.app.backup.BackupDataOutput; import android.app.backup.BackupAgentHelper; import android.app.backup.FullBackup; import android.app.backup.FullBackupDataOutput; +import android.app.backup.RecentsBackupHelper; import android.app.backup.WallpaperBackupHelper; import android.content.Context; import android.os.Environment; @@ -83,6 +84,8 @@ public class SystemBackupAgent extends BackupAgentHelper { } } addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys)); + addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this)); + super.onBackup(oldState, data, newState); } @@ -113,6 +116,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this, new String[] { WALLPAPER_IMAGE }, new String[] { WALLPAPER_IMAGE_KEY} )); + addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this)); try { super.onRestore(data, appVersionCode, newState); |