diff options
Diffstat (limited to 'core/java')
53 files changed, 2948 insertions, 2076 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/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/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 e3de44e..98a096c 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) { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 7fafc38..1de9b47 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1863,6 +1863,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) { @@ -1951,7 +1966,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..07e8dc5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -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 ad1cf44..57d53aa 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -182,8 +182,8 @@ public class DevicePolicyManager { * <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_DONT_DISABLE_SYSTEM_APPS = - "android.app.extra.PROVISIONING_DONT_DISABLE_SYSTEM_APPS"; + 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 @@ -3421,12 +3421,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/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/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 4c82efd..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; } 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..a73ba74 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; @@ -2864,10 +2865,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 interacting with the status bar. * * @see #getSystemService - * @see android.app.UsageStatsManager + * @see android.app.usage.UsageStatsManager * @hide */ public static final String USAGE_STATS_SERVICE = "usagestats"; @@ -2921,6 +2922,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 +3114,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..a13a2ea 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 diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index c848993..d469487 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; @@ -164,6 +165,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. 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/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/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/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 270d786..4135e8b 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 1f45f0a..3c12e06 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.java b/core/java/android/service/carriermessaging/CarrierMessagingService.java index 101f69b..7aea590 100644 --- a/core/java/android/service/carriermessaging/CarrierMessagingService.java +++ b/core/java/android/service/carriermessaging/CarrierMessagingService.java @@ -16,6 +16,7 @@ package android.service.carriermessaging; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.app.Service; @@ -93,7 +94,7 @@ public abstract class CarrierMessagingService extends Service { * @return True to keep an inbound SMS message and delivered to SMS apps. False to * drop the message. */ - public boolean onFilterSms(MessagePdu pdu, String format, int destPort) { + public boolean onFilterSms(@NonNull MessagePdu pdu, @NonNull String format, int destPort) { // optional return true; } @@ -105,9 +106,11 @@ public abstract class CarrierMessagingService extends Service { * @param format the format of the response PDU, typically "3gpp" or "3gpp2" * @param destAddress phone number of the recipient of the message * - * @return a {@link SendSmsResponse}. + * @return a possibly {code null} {@link SendSmsResponse}. Upon returning {@code null}, the SMS + * is sent using the carrier network. */ - public SendSmsResponse onSendTextSms(String text, String format, String destAddress) { + public @Nullable SendSmsResponse onSendTextSms( + @NonNull String text, @NonNull String format, @NonNull String destAddress) { // optional return null; } @@ -120,10 +123,11 @@ public abstract class CarrierMessagingService extends Service { * @param destAddress phone number of the recipient of the message * @param destPort the destination port * - * @return a {@link SendSmsResponse} + * @return a possibly {code null} {@link SendSmsResponse}. Upon returning {@code null}, the SMS + * is sent using the carrier network. */ - public SendSmsResponse onSendDataSms(byte[] data, String format, String destAddress, - int destPort) { + public @Nullable SendSmsResponse onSendDataSms(@NonNull byte[] data, @NonNull String format, + @NonNull String destAddress, int destPort) { // optional return null; } @@ -135,10 +139,11 @@ public abstract class CarrierMessagingService extends Service { * @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 {@link List} of {@link SendSmsResponse}, one for each message part. + * @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 List<SendSmsResponse> onSendMultipartTextSms(List<String> parts, String format, - String destAddress) { + public @Nullable List<SendSmsResponse> onSendMultipartTextSms(@NonNull List<String> parts, + @NonNull String format, @NonNull String destAddress) { // optional return null; } @@ -150,9 +155,10 @@ public abstract class CarrierMessagingService extends Service { * @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 {@link SendMmsResult}. + * @return a possibly {@code null} {@link SendMmsResult}. Upon returning {@code null}, the + * MMS is sent using the carrier network. */ - public SendMmsResult onSendMms(Uri pduUri, @Nullable String locationUrl) { + public @Nullable SendMmsResult onSendMms(@NonNull Uri pduUri, @Nullable String locationUrl) { // optional return null; } @@ -165,13 +171,13 @@ public abstract class CarrierMessagingService extends Service { * * @return a {@link SendMmsResult}. */ - public int onDownloadMms(Uri contentUri, String locationUrl) { + public int onDownloadMms(@NonNull Uri contentUri, @NonNull String locationUrl) { // optional return DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK; } @Override - public IBinder onBind(Intent intent) { + public @Nullable IBinder onBind(@NonNull Intent intent) { if (!SERVICE_INTERFACE.equals(intent.getAction())) { return null; } @@ -185,12 +191,24 @@ public abstract class CarrierMessagingService extends Service { private int mResult; private byte[] mSendConfPdu; - public SendMmsResult(int result, byte[] sendConfPdu) { + /** + * 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} */ @@ -199,9 +217,11 @@ public abstract class CarrierMessagingService extends Service { } /** - * @return the SendConf PDU, which confirms that the message was sent. + * Returns the SendConf PDU, which confirms that the message was sent. + * + * @return the SendConf PDU */ - public byte[] getSendConfPdu() { + public @Nullable byte[] getSendConfPdu() { return mSendConfPdu; } } @@ -219,12 +239,15 @@ public abstract class CarrierMessagingService extends Service { 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, byte[] ackPdu, int errorCode) { + public SendSmsResponse(int messageRef, @NonNull byte[] ackPdu, int errorCode) { mMessageRef = messageRef; mAckPdu = ackPdu; mErrorCode = errorCode; @@ -244,7 +267,7 @@ public abstract class CarrierMessagingService extends Service { * * @return the ackPdu */ - public byte[] getAckPdu() { + public @NonNull byte[] getAckPdu() { return mAckPdu; } diff --git a/core/java/android/service/carriermessaging/MessagePdu.java b/core/java/android/service/carriermessaging/MessagePdu.java index b81719f..3c78568 100644 --- a/core/java/android/service/carriermessaging/MessagePdu.java +++ b/core/java/android/service/carriermessaging/MessagePdu.java @@ -16,6 +16,7 @@ package android.service.carriermessaging; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; @@ -31,9 +32,14 @@ public final class MessagePdu implements Parcelable { private final List<byte[]> mPduList; /** + * Constructs a MessagePdu with the list of message PDUs. + * * @param pduList the list of message PDUs */ - public MessagePdu(List<byte[]> pduList) { + 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; } @@ -42,7 +48,7 @@ public final class MessagePdu implements Parcelable { * * @return the list of PDUs */ - public List<byte[]> getPdus() { + public @NonNull List<byte[]> getPdus() { return mPduList; } @@ -51,9 +57,6 @@ public final class MessagePdu implements Parcelable { return 0; } - /** - * Writes the PDU into a {@link Parcel}. - */ @Override public void writeToParcel(Parcel dest, int flags) { if (mPduList == null) { 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/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index e82057c..02297e3 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -383,7 +383,6 @@ public class StaticLayout extends Layout { okBottom = fitBottom; } } else { - final boolean moreChars; int endPos; int above, below, top, bottom; float currentTextWidth; @@ -395,7 +394,6 @@ public class StaticLayout extends Layout { top = okTop; bottom = okBottom; currentTextWidth = okWidth; - moreChars = (j + 1 < spanEnd); } else if (fit != here) { endPos = fit; above = fitAscent; @@ -403,7 +401,6 @@ public class StaticLayout extends Layout { top = fitTop; bottom = fitBottom; currentTextWidth = fitWidth; - moreChars = (j + 1 < spanEnd); } else { // must make progress, so take next character endPos = here + 1; @@ -417,7 +414,6 @@ public class StaticLayout extends Layout { top = fmTop; bottom = fmBottom; currentTextWidth = widths[here - paraStart]; - moreChars = (endPos < spanEnd); } v = out(source, here, endPos, @@ -425,7 +421,7 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji, needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, chs, widths, paraStart, ellipsize, ellipsizedWidth, - currentTextWidth, paint, moreChars); + currentTextWidth, paint, true); here = endPos; j = here - 1; // restart j-span loop from here, compensating for the j++ 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/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 5579c13..2c8a499 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()) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 77c1d7b..b54d462 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; @@ -11277,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); @@ -14411,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); + } - 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; + + 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(); - canvas.translate(-mScrollX, -mScrollY); + if (autoScale && scalingRequired) { + final float scale = attachInfo.mApplicationScale; + canvas.scale(scale, scale); + } - mPrivateFlags |= PFLAG_DRAWN; - if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated || - mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID; - } + canvas.translate(-mScrollX, -mScrollY); - // 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); + 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); + } - 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; } } @@ -14774,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); } /** @@ -14873,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( @@ -15478,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. @@ -15486,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; } } @@ -15505,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); } /** @@ -15861,7 +15856,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mOverlay.getOverlayView().setRight(newWidth); mOverlay.getOverlayView().setBottom(newHeight); } - mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID; + rebuildOutline(); } /** @@ -15897,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(); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index bae0b12..1a5ff26 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 * 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/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/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/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..06bb32c 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -1194,17 +1194,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 +1242,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; } 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/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/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/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 3ccced5..8d3db5b 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -384,8 +384,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 +404,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; } @@ -955,8 +971,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 +1119,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)); } /** @@ -1485,15 +1515,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; } |