diff options
Diffstat (limited to 'core/java/android')
96 files changed, 3147 insertions, 2001 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 9d6ee80..7efe189 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -548,6 +548,7 @@ public abstract class AccessibilityService extends Service { private static final int DO_ON_INTERRUPT = 20; private static final int DO_ON_ACCESSIBILITY_EVENT = 30; private static final int DO_ON_GESTURE = 40; + private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50; private final HandlerCaller mCaller; @@ -580,6 +581,11 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(message); } + public void clearAccessibilityNodeInfoCache() { + Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE); + mCaller.sendMessage(message); + } + public void executeMessage(Message message) { switch (message.what) { case DO_ON_ACCESSIBILITY_EVENT : @@ -611,6 +617,9 @@ public abstract class AccessibilityService extends Service { final int gestureId = message.arg1; mCallback.onGesture(gestureId); return; + case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: + AccessibilityInteractionClient.getInstance().clearCache(); + return; default : Log.w(LOG_TAG, "Unknown message type " + message.what); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index d459fd5..5d684e3 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -33,4 +33,6 @@ import android.view.accessibility.AccessibilityEvent; void onInterrupt(); void onGesture(int gesture); + + void clearAccessibilityNodeInfoCache(); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index dd50f3c..f33f503 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -18,6 +18,7 @@ package android.accessibilityservice; import android.os.Bundle; import android.accessibilityservice.AccessibilityServiceInfo; +import android.view.MagnificationSpec; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; @@ -44,9 +45,9 @@ interface IAccessibilityServiceConnection { * @param callback Callback which to receive the result. * @param flags Additional flags. * @param threadId The id of the calling thread. - * @return The current window scale, where zero means a failure. + * @return Whether the call succeeded. */ - float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long threadId); @@ -66,9 +67,9 @@ interface IAccessibilityServiceConnection { * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. - * @return The current window scale, where zero means a failure. + * @return Whether the call succeeded. */ - float findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, + boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); @@ -88,9 +89,9 @@ interface IAccessibilityServiceConnection { * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. - * @return The current window scale, where zero means a failure. + * @return Whether the call succeeded. */ - float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId, + boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId, int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); @@ -110,9 +111,9 @@ interface IAccessibilityServiceConnection { * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. - * @return The current window scale, where zero means a failure. + * @return Whether the call succeeded. */ - float findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, + boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); /** @@ -131,9 +132,9 @@ interface IAccessibilityServiceConnection { * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. - * @return The current window scale, where zero means a failure. + * @return Whether the call succeeded. */ - float focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, + boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); /** diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index bcb35d5..6d9bb1d 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -1846,7 +1846,7 @@ public class AccountManager { * Returns an intent to an {@link Activity} that prompts the user to choose from a list of * accounts. * The caller will then typically start the activity by calling - * <code>startActivityWithResult(intent, ...);</code>. + * <code>startActivityForResult(intent, ...);</code>. * <p> * On success the activity returns a Bundle with the account name and type specified using * keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}. diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java index 063e5a8..3ba4f26 100644 --- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java +++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java @@ -21,7 +21,6 @@ import android.os.ServiceManager; import android.os.INetworkManagementService; import android.content.Context; import android.net.ConnectivityManager; -import android.net.DhcpInfoInternal; import android.net.LinkCapabilities; import android.net.LinkProperties; import android.net.NetworkInfo; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 9e406d4..b20cf88 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -169,6 +169,25 @@ public abstract class ContentResolver { /** @hide */ public static final int SYNC_ERROR_INTERNAL = 8; + private static final String[] SYNC_ERROR_NAMES = new String[] { + "already-in-progress", + "authentication-error", + "io-error", + "parse-error", + "conflict", + "too-many-deletions", + "too-many-retries", + "internal-error", + }; + + /** @hide */ + static String syncErrorToString(int error) { + if (error < 1 || error > SYNC_ERROR_NAMES.length) { + return String.valueOf(error); + } + return SYNC_ERROR_NAMES[error - 1]; + } + public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1<<0; public static final int SYNC_OBSERVER_TYPE_PENDING = 1<<1; public static final int SYNC_OBSERVER_TYPE_ACTIVE = 1<<2; diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 4512e82..8bac888 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -226,6 +226,7 @@ public final class ContentService extends IContentService.Stub { } } + final int uid = Binder.getCallingUid(); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. long identityToken = clearCallingIdentity(); @@ -264,7 +265,7 @@ public final class ContentService extends IContentService.Stub { if (syncToNetwork) { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, + syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid, uri.getAuthority()); } } @@ -300,6 +301,7 @@ public final class ContentService extends IContentService.Stub { public void requestSync(Account account, String authority, Bundle extras) { ContentResolver.validateSyncExtrasBundle(extras); int userId = UserHandle.getCallingUserId(); + int uId = Binder.getCallingUid(); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. @@ -307,7 +309,7 @@ public final class ContentService extends IContentService.Stub { try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - syncManager.scheduleSync(account, userId, authority, extras, 0 /* no delay */, + syncManager.scheduleSync(account, userId, uId, authority, extras, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } finally { diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index e4b4b97..e428968 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -65,6 +65,7 @@ import com.google.android.collect.Maps; import com.google.android.collect.Sets; import java.io.FileDescriptor; +import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -201,7 +202,9 @@ public class SyncManager { private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (getConnectivityManager().getBackgroundDataSetting()) { - scheduleSync(null /* account */, UserHandle.USER_ALL, null /* authority */, + scheduleSync(null /* account */, UserHandle.USER_ALL, + SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED, + null /* authority */, new Bundle(), 0 /* delay */, false /* onlyThoseWithUnknownSyncableState */); } @@ -213,7 +216,8 @@ public class SyncManager { updateRunningAccounts(); // Kick off sync for everyone, since this was a radical account change - scheduleSync(null, UserHandle.USER_ALL, null, null, 0 /* no delay */, false); + scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null, + null, 0 /* no delay */, false); } }; @@ -351,14 +355,14 @@ public class SyncManager { SyncStorageEngine.init(context); mSyncStorageEngine = SyncStorageEngine.getSingleton(); mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { - public void onSyncRequest(Account account, int userId, String authority, + public void onSyncRequest(Account account, int userId, int reason, String authority, Bundle extras) { - scheduleSync(account, userId, authority, extras, 0, false); + scheduleSync(account, userId, reason, authority, extras, 0, false); } }); mSyncAdapters = new SyncAdaptersCache(mContext); - mSyncQueue = new SyncQueue(mSyncStorageEngine, mSyncAdapters); + mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters); HandlerThread syncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND); @@ -369,7 +373,9 @@ public class SyncManager { @Override public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) { if (!removed) { - scheduleSync(null, UserHandle.USER_ALL, type.authority, null, 0 /* no delay */, + scheduleSync(null, UserHandle.USER_ALL, + SyncOperation.REASON_SERVICE_CHANGED, + type.authority, null, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } @@ -498,6 +504,17 @@ public class SyncManager { * @param requestedAccount the account to sync, may be null to signify all accounts * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL, * then all users' accounts are considered. + * @param reason for sync request. If this is a positive integer, it is the Linux uid + * assigned to the process that requested the sync. If it's negative, the sync was requested by + * the SyncManager itself and could be one of the following: + * {@link SyncOperation#REASON_BACKGROUND_DATA_SETTINGS_CHANGED} + * {@link SyncOperation#REASON_ACCOUNTS_UPDATED} + * {@link SyncOperation#REASON_SERVICE_CHANGED} + * {@link SyncOperation#REASON_PERIODIC} + * {@link SyncOperation#REASON_IS_SYNCABLE} + * {@link SyncOperation#REASON_SYNC_AUTO} + * {@link SyncOperation#REASON_MASTER_SYNC_AUTO} + * {@link SyncOperation#REASON_USER_START} * @param requestedAuthority the authority to sync, may be null to indicate all authorities * @param extras a Map of SyncAdapter-specific information to control * syncs of a specific provider. Can be null. Is ignored @@ -505,8 +522,9 @@ public class SyncManager { * @param delay how many milliseconds in the future to wait before performing this * @param onlyThoseWithUnkownSyncableState */ - public void scheduleSync(Account requestedAccount, int userId, String requestedAuthority, - Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) { + public void scheduleSync(Account requestedAccount, int userId, int reason, + String requestedAuthority, Bundle extras, long delay, + boolean onlyThoseWithUnkownSyncableState) { boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); final boolean backgroundDataUsageAllowed = !mBootCompleted || @@ -632,8 +650,9 @@ public class SyncManager { + ", extras " + newExtras); } scheduleSyncOperation( - new SyncOperation(account.account, account.userId, source, authority, - newExtras, 0, backoffTime, delayUntil, allowParallelSyncs)); + new SyncOperation(account.account, account.userId, reason, source, + authority, newExtras, 0, backoffTime, delayUntil, + allowParallelSyncs)); } if (!onlyThoseWithUnkownSyncableState) { if (isLoggable) { @@ -645,17 +664,18 @@ public class SyncManager { + ", extras " + extras); } scheduleSyncOperation( - new SyncOperation(account.account, account.userId, source, authority, - extras, delay, backoffTime, delayUntil, allowParallelSyncs)); + new SyncOperation(account.account, account.userId, reason, source, + authority, extras, delay, backoffTime, delayUntil, + allowParallelSyncs)); } } } } - public void scheduleLocalSync(Account account, int userId, String authority) { + public void scheduleLocalSync(Account account, int userId, int reason, String authority) { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); - scheduleSync(account, userId, authority, extras, LOCAL_SYNC_DELAY, + scheduleSync(account, userId, reason, authority, extras, LOCAL_SYNC_DELAY, false /* onlyThoseWithUnkownSyncableState */); } @@ -881,6 +901,7 @@ public class SyncManager { + "sync in progress: " + operation); } scheduleSyncOperation(new SyncOperation(operation.account, operation.userId, + operation.reason, operation.syncSource, operation.authority, operation.extras, DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, @@ -912,8 +933,8 @@ public class SyncManager { // Schedule sync for any accounts under started user final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); for (Account account : accounts) { - scheduleSync(account, userId, null, null, 0 /* no delay */, - true /* onlyThoseWithUnknownSyncableState */); + scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, + 0 /* no delay */, true /* onlyThoseWithUnknownSyncableState */); } sendCheckAlarmsMessage(); @@ -1127,12 +1148,13 @@ public class SyncManager { pw.println(); pw.println("Active Syncs: " + mActiveSyncContexts.size()); + final PackageManager pm = mContext.getPackageManager(); for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) { final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000; pw.print(" "); pw.print(DateUtils.formatElapsedTime(durationInSeconds)); pw.print(" - "); - pw.print(activeSyncContext.mSyncOperation.dump(false)); + pw.print(activeSyncContext.mSyncOperation.dump(pm, false)); pw.println(); } @@ -1147,78 +1169,96 @@ public class SyncManager { pw.println(); pw.println("Sync Status"); for (AccountAndUser account : accounts) { - pw.print(" Account "); pw.print(account.account.name); - pw.print(" u"); pw.print(account.userId); - pw.print(" "); pw.print(account.account.type); - pw.println(":"); - for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : - mSyncAdapters.getAllServices(account.userId)) { + pw.printf("Account %s u%d %s\n", + account.account.name, account.userId, account.account.type); + + pw.println("======================================================================="); + final PrintTable table = new PrintTable(13); + table.set(0, 0, + "Authority", // 0 + "Syncable", // 1 + "Enabled", // 2 + "Delay", // 3 + "Loc", // 4 + "Poll", // 5 + "Per", // 6 + "Serv", // 7 + "User", // 8 + "Tot", // 9 + "Time", // 10 + "Last Sync", // 11 + "Periodic" // 12 + ); + + final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted = + Lists.newArrayList(); + sorted.addAll(mSyncAdapters.getAllServices(account.userId)); + Collections.sort(sorted, + new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() { + @Override + public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs, + RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) { + return lhs.type.authority.compareTo(rhs.type.authority); + } + }); + for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : sorted) { if (!syncAdapterType.type.accountType.equals(account.account.type)) { continue; } - + int row = table.getNumRows(); SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getOrCreateAuthority( account.account, account.userId, syncAdapterType.type.authority); SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings); - pw.print(" "); pw.print(settings.authority); - pw.println(":"); - pw.print(" settings:"); - pw.print(" " + (settings.syncable > 0 - ? "syncable" - : (settings.syncable == 0 ? "not syncable" : "not initialized"))); - pw.print(", " + (settings.enabled ? "enabled" : "disabled")); - if (settings.delayUntil > now) { - pw.print(", delay for " - + ((settings.delayUntil - now) / 1000) + " sec"); + + String authority = settings.authority; + if (authority.length() > 50) { + authority = authority.substring(authority.length() - 50); } - if (settings.backoffTime > now) { - pw.print(", backoff for " - + ((settings.backoffTime - now) / 1000) + " sec"); + table.set(row, 0, authority, settings.syncable, settings.enabled); + table.set(row, 4, + status.numSourceLocal, + status.numSourcePoll, + status.numSourcePeriodic, + status.numSourceServer, + status.numSourceUser, + status.numSyncs, + DateUtils.formatElapsedTime(status.totalElapsedTime / 1000)); + + + for (int i = 0; i < settings.periodicSyncs.size(); i++) { + final Pair<Bundle, Long> pair = settings.periodicSyncs.get(0); + final String period = String.valueOf(pair.second); + final String extras = pair.first.size() > 0 ? pair.first.toString() : ""; + final String next = formatTime(status.getPeriodicSyncTime(0) + + pair.second * 1000); + table.set(row + i * 2, 12, period + extras); + table.set(row + i * 2 + 1, 12, next); } - if (settings.backoffDelay > 0) { - pw.print(", the backoff increment is " + settings.backoffDelay / 1000 - + " sec"); + + int row1 = row; + if (settings.delayUntil > now) { + table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000); + if (settings.backoffTime > now) { + table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000); + table.set(row1++, 12, settings.backoffDelay / 1000); + } } - pw.println(); - for (int periodicIndex = 0; - periodicIndex < settings.periodicSyncs.size(); - periodicIndex++) { - Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex); - long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex); - long nextPeriodicTime = lastPeriodicTime + info.second * 1000; - pw.println(" periodic period=" + info.second - + ", extras=" + info.first - + ", next=" + formatTime(nextPeriodicTime)); - } - pw.print(" count: local="); pw.print(status.numSourceLocal); - pw.print(" poll="); pw.print(status.numSourcePoll); - pw.print(" periodic="); pw.print(status.numSourcePeriodic); - pw.print(" server="); pw.print(status.numSourceServer); - pw.print(" user="); pw.print(status.numSourceUser); - pw.print(" total="); pw.print(status.numSyncs); - pw.println(); - pw.print(" total duration: "); - pw.println(DateUtils.formatElapsedTime(status.totalElapsedTime/1000)); + if (status.lastSuccessTime != 0) { - pw.print(" SUCCESS: source="); - pw.print(SyncStorageEngine.SOURCES[status.lastSuccessSource]); - pw.print(" time="); - pw.println(formatTime(status.lastSuccessTime)); + table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource] + + " " + "SUCCESS"); + table.set(row1++, 11, formatTime(status.lastSuccessTime)); } if (status.lastFailureTime != 0) { - pw.print(" FAILURE: source="); - pw.print(SyncStorageEngine.SOURCES[ - status.lastFailureSource]); - pw.print(" initialTime="); - pw.print(formatTime(status.initialFailureTime)); - pw.print(" lastTime="); - pw.println(formatTime(status.lastFailureTime)); - int errCode = status.getLastFailureMesgAsInt(0); - pw.print(" message: "); pw.println( - getLastFailureMessage(errCode) + " (" + errCode + ")"); + table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource] + + " " + "FAILURE"); + table.set(row1++, 11, formatTime(status.lastFailureTime)); + //noinspection UnusedAssignment + table.set(row1++, 11, status.lastFailureMesg); } } + table.writeTo(pw); } } @@ -1412,9 +1452,9 @@ public class SyncManager { pw.println(); pw.println("Recent Sync History"); - final String format = " %-" + maxAccount + "s %s\n"; + final String format = " %-" + maxAccount + "s %-" + maxAuthority + "s %s\n"; final Map<String, Long> lastTimeMap = Maps.newHashMap(); - + final PackageManager pm = mContext.getPackageManager(); for (int i = 0; i < N; i++) { SyncStorageEngine.SyncHistoryItem item = items.get(i); SyncStorageEngine.AuthorityInfo authority @@ -1459,7 +1499,8 @@ public class SyncManager { SyncStorageEngine.SOURCES[item.source], ((float) elapsedTime) / 1000, diffString); - pw.printf(format, accountKey, authorityName); + pw.printf(format, accountKey, authorityName, + SyncOperation.reasonToString(pm, item.reason)); if (item.event != SyncStorageEngine.EVENT_STOP || item.upstreamActivity != 0 @@ -1474,6 +1515,37 @@ public class SyncManager { pw.printf(" mesg=%s\n", item.mesg); } } + pw.println(); + pw.println("Recent Sync History Extras"); + for (int i = 0; i < N; i++) { + final SyncStorageEngine.SyncHistoryItem item = items.get(i); + final Bundle extras = item.extras; + if (extras == null || extras.size() == 0) { + continue; + } + final SyncStorageEngine.AuthorityInfo authority + = mSyncStorageEngine.getAuthority(item.authorityId); + final String authorityName; + final String accountKey; + if (authority != null) { + authorityName = authority.authority; + accountKey = authority.account.name + "/" + authority.account.type + + " u" + authority.userId; + } else { + authorityName = "Unknown"; + accountKey = "Unknown"; + } + final Time time = new Time(); + final long eventTime = item.eventTime; + time.set(eventTime); + + pw.printf(" #%-3d: %s %8s ", + i + 1, + formatTime(eventTime), + SyncStorageEngine.SOURCES[item.source]); + + pw.printf(format, accountKey, authorityName, extras); + } } } @@ -1888,6 +1960,7 @@ public class SyncManager { } scheduleSyncOperation( new SyncOperation(info.account, info.userId, + SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_PERIODIC, info.authority, extras, 0 /* delay */, backoff != null ? backoff.first : 0, @@ -2289,7 +2362,8 @@ public class SyncManager { } // reschedule the sync if so indicated by the syncResult maybeRescheduleSync(syncResult, syncOperation); - historyMessage = Integer.toString(syncResultToErrorNumber(syncResult)); + historyMessage = ContentResolver.syncErrorToString( + syncResultToErrorNumber(syncResult)); // TODO: set these correctly when the SyncResult is extended to include it downstreamActivity = 0; upstreamActivity = 0; @@ -2327,6 +2401,7 @@ public class SyncManager { if (syncResult != null && syncResult.fullSyncRequested) { scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId, + syncOperation.reason, syncOperation.syncSource, syncOperation.authority, new Bundle(), 0, syncOperation.backoff, syncOperation.delayUntil, syncOperation.allowParallelSyncs)); @@ -2606,8 +2681,10 @@ public class SyncManager { syncOperation.account.name.hashCode()); return mSyncStorageEngine.insertStartSyncEvent( - syncOperation.account, syncOperation.userId, syncOperation.authority, - now, source, syncOperation.isInitialization()); + syncOperation.account, syncOperation.userId, syncOperation.reason, + syncOperation.authority, + now, source, syncOperation.isInitialization(), syncOperation.extras + ); } public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, @@ -2629,4 +2706,66 @@ public class SyncManager { } return false; } + + static class PrintTable { + private ArrayList<Object[]> mTable = Lists.newArrayList(); + private final int mCols; + + PrintTable(int cols) { + mCols = cols; + } + + void set(int row, int col, Object... values) { + if (col + values.length > mCols) { + throw new IndexOutOfBoundsException("Table only has " + mCols + + " columns. can't set " + values.length + " at column " + col); + } + for (int i = mTable.size(); i <= row; i++) { + final Object[] list = new Object[mCols]; + mTable.add(list); + for (int j = 0; j < mCols; j++) { + list[j] = ""; + } + } + System.arraycopy(values, 0, mTable.get(row), col, values.length); + } + + void writeTo(PrintWriter out) { + final String[] formats = new String[mCols]; + int totalLength = 0; + for (int col = 0; col < mCols; ++col) { + int maxLength = 0; + for (Object[] row : mTable) { + final int length = row[col].toString().length(); + if (length > maxLength) { + maxLength = length; + } + } + totalLength += maxLength; + formats[col] = String.format("%%-%ds", maxLength); + } + printRow(out, formats, mTable.get(0)); + totalLength += (mCols - 1) * 2; + for (int i = 0; i < totalLength; ++i) { + out.print("-"); + } + out.println(); + for (int i = 1, mTableSize = mTable.size(); i < mTableSize; i++) { + Object[] row = mTable.get(i); + printRow(out, formats, row); + } + } + + private void printRow(PrintWriter out, String[] formats, Object[] row) { + for (int j = 0, rowLength = row.length; j < rowLength; j++) { + out.printf(String.format(formats[j], row[j].toString())); + out.print(" "); + } + out.println(); + } + + public int getNumRows() { + return mTable.size(); + } + } } diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java index 6611fcd..a4c2cff 100644 --- a/core/java/android/content/SyncOperation.java +++ b/core/java/android/content/SyncOperation.java @@ -17,6 +17,7 @@ package android.content; import android.accounts.Account; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.SystemClock; @@ -25,8 +26,29 @@ import android.os.SystemClock; * @hide */ public class SyncOperation implements Comparable { + public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; + public static final int REASON_ACCOUNTS_UPDATED = -2; + public static final int REASON_SERVICE_CHANGED = -3; + public static final int REASON_PERIODIC = -4; + public static final int REASON_IS_SYNCABLE = -5; + public static final int REASON_SYNC_AUTO = -6; + public static final int REASON_MASTER_SYNC_AUTO = -7; + public static final int REASON_USER_START = -8; + + private static String[] REASON_NAMES = new String[] { + "DataSettingsChanged", + "AccountsUpdated", + "ServiceChanged", + "Periodic", + "IsSyncable", + "AutoSync", + "MasterSyncAuto", + "UserStart", + }; + public final Account account; public final int userId; + public final int reason; public int syncSource; public String authority; public final boolean allowParallelSyncs; @@ -39,10 +61,12 @@ public class SyncOperation implements Comparable { public long delayUntil; public long effectiveRunTime; - public SyncOperation(Account account, int userId, int source, String authority, Bundle extras, - long delayInMs, long backoff, long delayUntil, boolean allowParallelSyncs) { + public SyncOperation(Account account, int userId, int reason, int source, String authority, + Bundle extras, long delayInMs, long backoff, long delayUntil, + boolean allowParallelSyncs) { this.account = account; this.userId = userId; + this.reason = reason; this.syncSource = source; this.authority = authority; this.allowParallelSyncs = allowParallelSyncs; @@ -78,6 +102,7 @@ public class SyncOperation implements Comparable { SyncOperation(SyncOperation other) { this.account = other.account; this.userId = other.userId; + this.reason = other.reason; this.syncSource = other.syncSource; this.authority = other.authority; this.extras = new Bundle(other.extras); @@ -91,10 +116,10 @@ public class SyncOperation implements Comparable { } public String toString() { - return dump(true); + return dump(null, true); } - public String dump(boolean useOneLine) { + public String dump(PackageManager pm, boolean useOneLine) { StringBuilder sb = new StringBuilder() .append(account.name) .append(" u") @@ -110,6 +135,8 @@ public class SyncOperation implements Comparable { if (expedited) { sb.append(", EXPEDITED"); } + sb.append(", reason: "); + sb.append(reasonToString(pm, reason)); if (!useOneLine && !extras.keySet().isEmpty()) { sb.append("\n "); extrasToStringBuilder(extras, sb); @@ -117,6 +144,31 @@ public class SyncOperation implements Comparable { return sb.toString(); } + public static String reasonToString(PackageManager pm, int reason) { + if (reason >= 0) { + if (pm != null) { + final String[] packages = pm.getPackagesForUid(reason); + if (packages != null && packages.length == 1) { + return packages[0]; + } + final String name = pm.getNameForUid(reason); + if (name != null) { + return name; + } + return String.valueOf(reason); + } else { + return String.valueOf(reason); + } + } else { + final int index = -reason - 1; + if (index >= REASON_NAMES.length) { + return String.valueOf(reason); + } else { + return REASON_NAMES[index]; + } + } + } + public boolean isInitialization() { return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); } diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java index c9a325e..c09703c 100644 --- a/core/java/android/content/SyncQueue.java +++ b/core/java/android/content/SyncQueue.java @@ -17,6 +17,8 @@ package android.content; import android.accounts.Account; +import android.content.pm.PackageManager; +import android.content.pm.RegisteredServicesCache; import android.content.pm.RegisteredServicesCache.ServiceInfo; import android.os.SystemClock; import android.os.UserHandle; @@ -40,15 +42,17 @@ import java.util.Map; */ public class SyncQueue { private static final String TAG = "SyncManager"; - private final SyncStorageEngine mSyncStorageEngine; private final SyncAdaptersCache mSyncAdapters; + private final PackageManager mPackageManager; // A Map of SyncOperations operationKey -> SyncOperation that is designed for // quick lookup of an enqueued SyncOperation. private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap(); - public SyncQueue(SyncStorageEngine syncStorageEngine, final SyncAdaptersCache syncAdapters) { + public SyncQueue(PackageManager packageManager, SyncStorageEngine syncStorageEngine, + final SyncAdaptersCache syncAdapters) { + mPackageManager = packageManager; mSyncStorageEngine = syncStorageEngine; mSyncAdapters = syncAdapters; } @@ -67,8 +71,8 @@ public class SyncQueue { continue; } SyncOperation syncOperation = new SyncOperation( - op.account, op.userId, op.syncSource, op.authority, op.extras, 0 /* delay */, - backoff != null ? backoff.first : 0, + op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras, + 0 /* delay */, backoff != null ? backoff.first : 0, mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority), syncAdapterInfo.type.allowParallelSyncs()); syncOperation.expedited = op.expedited; @@ -112,7 +116,7 @@ public class SyncQueue { operation.pendingOperation = pop; if (operation.pendingOperation == null) { pop = new SyncStorageEngine.PendingOperation( - operation.account, operation.userId, operation.syncSource, + operation.account, operation.userId, operation.reason, operation.syncSource, operation.authority, operation.extras, operation.expedited); pop = mSyncStorageEngine.insertIntoPending(pop); if (pop == null) { @@ -214,7 +218,7 @@ public class SyncQueue { sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000)); } sb.append(" - "); - sb.append(operation.dump(false)).append("\n"); + sb.append(operation.dump(mPackageManager, false)).append("\n"); } } } diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 1ecab09..8d9b8e0 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -26,7 +26,6 @@ import org.xmlpull.v1.XmlSerializer; import android.accounts.Account; import android.accounts.AccountAndUser; -import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; @@ -147,6 +146,7 @@ public class SyncStorageEngine extends Handler { public static class PendingOperation { final Account account; final int userId; + final int reason; final int syncSource; final String authority; final Bundle extras; // note: read-only. @@ -155,11 +155,12 @@ public class SyncStorageEngine extends Handler { int authorityId; byte[] flatExtras; - PendingOperation(Account account, int userId, int source, + PendingOperation(Account account, int userId, int reason,int source, String authority, Bundle extras, boolean expedited) { this.account = account; this.userId = userId; this.syncSource = source; + this.reason = reason; this.authority = authority; this.extras = extras != null ? new Bundle(extras) : extras; this.expedited = expedited; @@ -169,6 +170,7 @@ public class SyncStorageEngine extends Handler { PendingOperation(PendingOperation other) { this.account = other.account; this.userId = other.userId; + this.reason = other.reason; this.syncSource = other.syncSource; this.authority = other.authority; this.extras = other.extras; @@ -247,6 +249,8 @@ public class SyncStorageEngine extends Handler { long downstreamActivity; String mesg; boolean initialization; + Bundle extras; + int reason; } public static class DayStats { @@ -266,10 +270,12 @@ public class SyncStorageEngine extends Handler { * Called when a sync is needed on an account(s) due to some change in state. * @param account * @param userId + * @param reason * @param authority * @param extras */ - public void onSyncRequest(Account account, int userId, String authority, Bundle extras); + public void onSyncRequest(Account account, int userId, int reason, String authority, + Bundle extras); } // Primary list of all syncable authorities. Also our global lock. @@ -503,7 +509,8 @@ public class SyncStorageEngine extends Handler { } if (sync) { - requestSync(account, userId, providerName, new Bundle()); + requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName, + new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } @@ -555,7 +562,8 @@ public class SyncStorageEngine extends Handler { } if (syncable > 0) { - requestSync(account, userId, providerName, new Bundle()); + requestSync(account, userId, SyncOperation.REASON_IS_SYNCABLE, providerName, + new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } @@ -788,7 +796,8 @@ public class SyncStorageEngine extends Handler { writeAccountInfoLocked(); } if (flag) { - requestSync(null, userId, null, new Bundle()); + requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null, + new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT); @@ -1051,8 +1060,8 @@ public class SyncStorageEngine extends Handler { /** * Note that sync has started for the given account and authority. */ - public long insertStartSyncEvent(Account accountName, int userId, String authorityName, - long now, int source, boolean initialization) { + public long insertStartSyncEvent(Account accountName, int userId, int reason, + String authorityName, long now, int source, boolean initialization, Bundle extras) { long id; synchronized (mAuthorities) { if (DEBUG) { @@ -1071,6 +1080,8 @@ public class SyncStorageEngine extends Handler { if (mNextHistoryId < 0) mNextHistoryId = 0; item.eventTime = now; item.source = source; + item.reason = reason; + item.extras = extras; item.event = EVENT_START; mSyncHistory.add(0, item); while (mSyncHistory.size() > MAX_HISTORY) { @@ -2046,7 +2057,7 @@ public class SyncStorageEngine extends Handler { } } - public static final int PENDING_OPERATION_VERSION = 2; + public static final int PENDING_OPERATION_VERSION = 3; /** * Read all pending operations back in to the initial engine state. @@ -2075,6 +2086,7 @@ public class SyncStorageEngine extends Handler { } else { expedited = false; } + int reason = in.readInt(); AuthorityInfo authority = mAuthorities.get(authorityId); if (authority != null) { Bundle extras; @@ -2086,13 +2098,14 @@ public class SyncStorageEngine extends Handler { extras = new Bundle(); } PendingOperation op = new PendingOperation( - authority.account, authority.userId, syncSource, + authority.account, authority.userId, reason, syncSource, authority.authority, extras, expedited); op.authorityId = authorityId; op.flatExtras = flatExtras; if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account + " auth=" + op.authority + " src=" + op.syncSource + + " reason=" + op.reason + " expedited=" + op.expedited + " extras=" + op.extras); mPendingOperations.add(op); @@ -2112,6 +2125,7 @@ public class SyncStorageEngine extends Handler { } out.writeByteArray(op.flatExtras); out.writeInt(op.expedited ? 1 : 0); + out.writeInt(op.reason); } /** @@ -2206,14 +2220,15 @@ public class SyncStorageEngine extends Handler { return bundle; } - private void requestSync(Account account, int userId, String authority, Bundle extras) { + private void requestSync(Account account, int userId, int reason, String authority, + Bundle extras) { // If this is happening in the system process, then call the syncrequest listener // to make a request back to the SyncManager directly. // If this is probably a test instance, then call back through the ContentResolver // which will know which userId to apply based on the Binder id. if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID && mSyncRequestListener != null) { - mSyncRequestListener.onSyncRequest(account, userId, authority, extras); + mSyncRequestListener.onSyncRequest(account, userId, reason, authority, extras); } else { ContentResolver.requestSync(account, authority, extras); } diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java index 63e33ce..78180b1 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -16,6 +16,7 @@ package android.content.res; +import android.graphics.Color; import android.text.*; import android.text.style.*; import android.util.Log; @@ -24,7 +25,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; -import com.android.internal.util.XmlUtils; +import java.util.Arrays; /** * Conveniences for retrieving data out of a compiled string resource. @@ -33,7 +34,7 @@ import com.android.internal.util.XmlUtils; */ final class StringBlock { private static final String TAG = "AssetManager"; - private static final boolean localLOGV = false || false; + private static final boolean localLOGV = false; private final int mNative; private final boolean mUseSparse; @@ -82,7 +83,7 @@ final class StringBlock { CharSequence res = str; int[] style = nativeGetStyle(mNative, idx); if (localLOGV) Log.v(TAG, "Got string: " + str); - if (localLOGV) Log.v(TAG, "Got styles: " + style); + if (localLOGV) Log.v(TAG, "Got styles: " + Arrays.toString(style)); if (style != null) { if (mStyleIDs == null) { mStyleIDs = new StyleIDs(); @@ -139,8 +140,12 @@ final class StringBlock { } protected void finalize() throws Throwable { - if (mOwnsNative) { - nativeDestroy(mNative); + try { + super.finalize(); + } finally { + if (mOwnsNative) { + nativeDestroy(mNative); + } } } @@ -236,19 +241,31 @@ final class StringBlock { sub = subtag(tag, ";fgcolor="); if (sub != null) { - int color = XmlUtils.convertValueToUnsignedInt(sub, -1); - buffer.setSpan(new ForegroundColorSpan(color), + buffer.setSpan(getColor(sub, true), style[i+1], style[i+2]+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } + sub = subtag(tag, ";color="); + if (sub != null) { + buffer.setSpan(getColor(sub, true), + style[i+1], style[i+2]+1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + sub = subtag(tag, ";bgcolor="); if (sub != null) { - int color = XmlUtils.convertValueToUnsignedInt(sub, -1); - buffer.setSpan(new BackgroundColorSpan(color), + buffer.setSpan(getColor(sub, false), style[i+1], style[i+2]+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } + + sub = subtag(tag, ";face="); + if (sub != null) { + buffer.setSpan(new TypefaceSpan(sub), + style[i+1], style[i+2]+1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } else if (tag.startsWith("a;")) { String sub; @@ -289,6 +306,48 @@ final class StringBlock { } /** + * Returns a span for the specified color string representation. + * If the specified string does not represent a color (null, empty, etc.) + * the color black is returned instead. + * + * @param color The color as a string. Can be a resource reference, + * HTML hexadecimal, octal or a name + * @param foreground True if the color will be used as the foreground color, + * false otherwise + * + * @return A CharacterStyle + * + * @see Color#getHtmlColor(String) + */ + private static CharacterStyle getColor(String color, boolean foreground) { + int c = 0xff000000; + + if (!TextUtils.isEmpty(color)) { + if (color.startsWith("@")) { + Resources res = Resources.getSystem(); + String name = color.substring(1); + int colorRes = res.getIdentifier(name, "color", "android"); + if (colorRes != 0) { + ColorStateList colors = res.getColorStateList(colorRes); + if (foreground) { + return new TextAppearanceSpan(null, 0, 0, colors, null); + } else { + c = colors.getDefaultColor(); + } + } + } else { + c = Color.getHtmlColor(color); + } + } + + if (foreground) { + return new ForegroundColorSpan(c); + } else { + return new BackgroundColorSpan(c); + } + } + + /** * If a translator has messed up the edges of paragraph-level markup, * fix it to actually cover the entire paragraph that it is attached to * instead of just whatever range they put it on. @@ -423,11 +482,11 @@ final class StringBlock { + ": " + nativeGetSize(mNative)); } - private static final native int nativeCreate(byte[] data, + private static native int nativeCreate(byte[] data, int offset, int size); - private static final native int nativeGetSize(int obj); - private static final native String nativeGetString(int obj, int idx); - private static final native int[] nativeGetStyle(int obj, int idx); - private static final native void nativeDestroy(int obj); + private static native int nativeGetSize(int obj); + private static native String nativeGetString(int obj, int idx); + private static native int[] nativeGetStyle(int obj, int idx); + private static native void nativeDestroy(int obj); } diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java index 5ef122b..f50cdef 100644 --- a/core/java/android/hardware/SerialPort.java +++ b/core/java/android/hardware/SerialPort.java @@ -82,7 +82,9 @@ public class SerialPort { } /** - * Reads data into the provided buffer + * Reads data into the provided buffer. + * Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is + * unchanged after a call to this method. * * @param buffer to read into * @return number of bytes read @@ -98,7 +100,9 @@ public class SerialPort { } /** - * Writes data from provided buffer + * Writes data from provided buffer. + * Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is + * unchanged after a call to this method. * * @param buffer to write * @param length number of bytes to write diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 6f1cc94..99624cc 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1955,7 +1955,7 @@ public class InputMethodService extends AbstractInputMethodService { ic.sendKeyEvent(new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); - ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, + ic.sendKeyEvent(new KeyEvent(eventTime, SystemClock.uptimeMillis(), KeyEvent.ACTION_UP, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 6ff1a33..a8a68d0 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -328,6 +328,18 @@ public class ConnectivityManager { /** {@hide} */ public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P; + /** + * If you want to set the default network preference,you can directly + * change the networkAttributes array in framework's config.xml. + * + * @deprecated Since we support so many more networks now, the single + * network default network preference can't really express + * the heirarchy. Instead, the default is defined by the + * networkAttributes in config.xml. You can determine + * the current value by calling {@link #getNetworkPreference()} + * from an App. + */ + @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI; /** diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java index e2660e4..2b359eb 100644 --- a/core/java/android/net/DhcpInfo.java +++ b/core/java/android/net/DhcpInfo.java @@ -22,16 +22,17 @@ import java.net.InetAddress; /** * A simple object for retrieving the results of a DHCP request. + * @deprecated - use LinkProperties - To be removed 11/2013 + * STOPSHIP - make sure we expose LinkProperties through ConnectivityManager */ public class DhcpInfo implements Parcelable { public int ipAddress; public int gateway; public int netmask; - public int dns1; public int dns2; - public int serverAddress; + public int leaseDuration; public DhcpInfo() { diff --git a/core/java/android/net/DhcpInfoInternal.java b/core/java/android/net/DhcpInfoInternal.java deleted file mode 100644 index f3508c1..0000000 --- a/core/java/android/net/DhcpInfoInternal.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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.net; - -import android.text.TextUtils; -import android.util.Log; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -/** - * A simple object for retrieving the results of a DHCP request. - * Replaces (internally) the IPv4-only DhcpInfo class. - * @hide - */ -public class DhcpInfoInternal { - private final static String TAG = "DhcpInfoInternal"; - public String ipAddress; - public int prefixLength; - - public String dns1; - public String dns2; - - public String serverAddress; - public int leaseDuration; - - /** - * Vendor specific information (from RFC 2132). - */ - public String vendorInfo; - - private Collection<RouteInfo> mRoutes; - - public DhcpInfoInternal() { - mRoutes = new ArrayList<RouteInfo>(); - } - - public void addRoute(RouteInfo routeInfo) { - mRoutes.add(routeInfo); - } - - public Collection<RouteInfo> getRoutes() { - return Collections.unmodifiableCollection(mRoutes); - } - - private int convertToInt(String addr) { - if (addr != null) { - try { - InetAddress inetAddress = NetworkUtils.numericToInetAddress(addr); - if (inetAddress instanceof Inet4Address) { - return NetworkUtils.inetAddressToInt(inetAddress); - } - } catch (IllegalArgumentException e) {} - } - return 0; - } - - public DhcpInfo makeDhcpInfo() { - DhcpInfo info = new DhcpInfo(); - info.ipAddress = convertToInt(ipAddress); - for (RouteInfo route : mRoutes) { - if (route.isDefaultRoute()) { - info.gateway = convertToInt(route.getGateway().getHostAddress()); - break; - } - } - try { - InetAddress inetAddress = NetworkUtils.numericToInetAddress(ipAddress); - info.netmask = NetworkUtils.prefixLengthToNetmaskInt(prefixLength); - } catch (IllegalArgumentException e) {} - info.dns1 = convertToInt(dns1); - info.dns2 = convertToInt(dns2); - info.serverAddress = convertToInt(serverAddress); - info.leaseDuration = leaseDuration; - return info; - } - - public LinkAddress makeLinkAddress() { - if (TextUtils.isEmpty(ipAddress)) { - Log.e(TAG, "makeLinkAddress with empty ipAddress"); - return null; - } - return new LinkAddress(NetworkUtils.numericToInetAddress(ipAddress), prefixLength); - } - - public LinkProperties makeLinkProperties() { - LinkProperties p = new LinkProperties(); - p.addLinkAddress(makeLinkAddress()); - for (RouteInfo route : mRoutes) { - p.addRoute(route); - } - //if empty, connectivity configures default DNS - if (TextUtils.isEmpty(dns1) == false) { - p.addDns(NetworkUtils.numericToInetAddress(dns1)); - } else { - Log.d(TAG, "makeLinkProperties with empty dns1!"); - } - if (TextUtils.isEmpty(dns2) == false) { - p.addDns(NetworkUtils.numericToInetAddress(dns2)); - } else { - Log.d(TAG, "makeLinkProperties with empty dns2!"); - } - return p; - } - - /* Updates the DHCP fields that need to be retained from - * original DHCP request if the DHCP renewal shows them as - * being empty - */ - public void updateFromDhcpRequest(DhcpInfoInternal orig) { - if (orig == null) return; - - if (TextUtils.isEmpty(dns1)) { - dns1 = orig.dns1; - } - - if (TextUtils.isEmpty(dns2)) { - dns2 = orig.dns2; - } - - if (mRoutes.size() == 0) { - for (RouteInfo route : orig.getRoutes()) { - addRoute(route); - } - } - } - - /** - * Test if this DHCP lease includes vendor hint that network link is - * metered, and sensitive to heavy data transfers. - */ - public boolean hasMeteredHint() { - if (vendorInfo != null) { - return vendorInfo.contains("ANDROID_METERED"); - } else { - return false; - } - } - - public String toString() { - String routeString = ""; - for (RouteInfo route : mRoutes) routeString += route.toString() + " | "; - return "addr: " + ipAddress + "/" + prefixLength + - " mRoutes: " + routeString + - " dns: " + dns1 + "," + dns2 + - " dhcpServer: " + serverAddress + - " leaseDuration: " + leaseDuration; - } -} diff --git a/core/java/android/util/Pool.java b/core/java/android/net/DhcpResults.aidl index 8cd4f3e..f4db3c3 100644 --- a/core/java/android/util/Pool.java +++ b/core/java/android/net/DhcpResults.aidl @@ -1,11 +1,11 @@ -/* - * Copyright (C) 2009 The Android Open Source Project +/** + * Copyright (c) 2012, 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 + * 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, @@ -14,12 +14,6 @@ * limitations under the License. */ -package android.util; +package android.net; -/** - * @hide - */ -public interface Pool<T extends Poolable<T>> { - public abstract T acquire(); - public abstract void release(T element); -} +parcelable DhcpResults; diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java new file mode 100644 index 0000000..2f300de --- /dev/null +++ b/core/java/android/net/DhcpResults.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.os.Parcelable; +import android.os.Parcel; +import android.util.Log; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * A simple object for retrieving the results of a DHCP request. + * Optimized (attempted) for that jni interface + * TODO - remove when DhcpInfo is deprecated. Move the remaining api to LinkProperties. + * @hide + */ +public class DhcpResults implements Parcelable { + private static final String TAG = "DhcpResults"; + + public final LinkProperties linkProperties; + + public InetAddress serverAddress; + + /** + * Vendor specific information (from RFC 2132). + */ + public String vendorInfo; + + public int leaseDuration; + + public DhcpResults() { + linkProperties = new LinkProperties(); + } + + /** copy constructor */ + public DhcpResults(DhcpResults source) { + if (source != null) { + linkProperties = new LinkProperties(source.linkProperties); + serverAddress = source.serverAddress; + leaseDuration = source.leaseDuration; + vendorInfo = source.vendorInfo; + } else { + linkProperties = new LinkProperties(); + } + } + + public DhcpResults(LinkProperties lp) { + linkProperties = new LinkProperties(lp); + } + + /** + * Updates the DHCP fields that need to be retained from + * original DHCP request if the current renewal shows them + * being empty. + */ + public void updateFromDhcpRequest(DhcpResults orig) { + if (orig == null || orig.linkProperties == null) return; + if (linkProperties.getRoutes().size() == 0) { + for (RouteInfo r : orig.linkProperties.getRoutes()) linkProperties.addRoute(r); + } + if (linkProperties.getDnses().size() == 0) { + for (InetAddress d : orig.linkProperties.getDnses()) linkProperties.addDns(d); + } + } + + /** + * Test if this DHCP lease includes vendor hint that network link is + * metered, and sensitive to heavy data transfers. + */ + public boolean hasMeteredHint() { + if (vendorInfo != null) { + return vendorInfo.contains("ANDROID_METERED"); + } else { + return false; + } + } + + public void clear() { + linkProperties.clear(); + serverAddress = null; + vendorInfo = null; + leaseDuration = 0; + } + + @Override + public String toString() { + StringBuffer str = new StringBuffer(linkProperties.toString()); + + str.append(" DHCP server ").append(serverAddress); + str.append(" Vendor info ").append(vendorInfo); + str.append(" lease ").append(leaseDuration).append(" seconds"); + + return str.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + + if (!(obj instanceof DhcpResults)) return false; + + DhcpResults target = (DhcpResults)obj; + + if (linkProperties == null) { + if (target.linkProperties != null) return false; + } else if (!linkProperties.equals(target.linkProperties)) return false; + if (serverAddress == null) { + if (target.serverAddress != null) return false; + } else if (!serverAddress.equals(target.serverAddress)) return false; + if (vendorInfo == null) { + if (target.vendorInfo != null) return false; + } else if (!vendorInfo.equals(target.vendorInfo)) return false; + if (leaseDuration != target.leaseDuration) return false; + + return true; + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + linkProperties.writeToParcel(dest, flags); + + dest.writeInt(leaseDuration); + + if (serverAddress != null) { + dest.writeByte((byte)1); + dest.writeByteArray(serverAddress.getAddress()); + } else { + dest.writeByte((byte)0); + } + + dest.writeString(vendorInfo); + } + + /** Implement the Parcelable interface */ + public static final Creator<DhcpResults> CREATOR = + new Creator<DhcpResults>() { + public DhcpResults createFromParcel(Parcel in) { + DhcpResults prop = new DhcpResults((LinkProperties)in.readParcelable(null)); + + prop.leaseDuration = in.readInt(); + + if (in.readByte() == 1) { + try { + prop.serverAddress = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) {} + } + + prop.vendorInfo = in.readString(); + + return prop; + } + + public DhcpResults[] newArray(int size) { + return new DhcpResults[size]; + } + }; + + // Utils for jni population - false on success + public void setInterfaceName(String interfaceName) { + linkProperties.setInterfaceName(interfaceName); + } + + public boolean addLinkAddress(String addrString, int prefixLength) { + InetAddress addr; + try { + addr = NetworkUtils.numericToInetAddress(addrString); + } catch (IllegalArgumentException e) { + Log.e(TAG, "addLinkAddress failed with addrString " + addrString); + return true; + } + + LinkAddress linkAddress = new LinkAddress(addr, prefixLength); + linkProperties.addLinkAddress(linkAddress); + + RouteInfo routeInfo = new RouteInfo(linkAddress); + linkProperties.addRoute(routeInfo); + return false; + } + + public boolean addGateway(String addrString) { + try { + linkProperties.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(addrString))); + } catch (IllegalArgumentException e) { + Log.e(TAG, "addGateway failed with addrString " + addrString); + return true; + } + return false; + } + + public boolean addDns(String addrString) { + try { + linkProperties.addDns(NetworkUtils.numericToInetAddress(addrString)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "addDns failed with addrString " + addrString); + return true; + } + return false; + } + + public boolean setServerAddress(String addrString) { + try { + serverAddress = NetworkUtils.numericToInetAddress(addrString); + } catch (IllegalArgumentException e) { + Log.e(TAG, "setServerAddress failed with addrString " + addrString); + return true; + } + return false; + } + + public void setLeaseDuration(int duration) { + leaseDuration = duration; + } + + public void setVendorInfo(String info) { + vendorInfo = info; + } + + public void setDomains(String domains) { + linkProperties.setDomains(domains); + } +} diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index 8dc900e..fd22b10 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -26,7 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.DhcpInfoInternal; +import android.net.DhcpResults; import android.net.NetworkUtils; import android.os.Message; import android.os.PowerManager; @@ -64,7 +64,7 @@ public class DhcpStateMachine extends StateMachine { private static final String WAKELOCK_TAG = "DHCP"; //Remember DHCP configuration from first request - private DhcpInfoInternal mDhcpInfo; + private DhcpResults mDhcpResults; private static final int DHCP_RENEW = 0; private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW"; @@ -348,23 +348,21 @@ public class DhcpStateMachine extends StateMachine { private boolean runDhcp(DhcpAction dhcpAction) { boolean success = false; - DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal(); + DhcpResults dhcpResults = new DhcpResults(); if (dhcpAction == DhcpAction.START) { /* Stop any existing DHCP daemon before starting new */ NetworkUtils.stopDhcp(mInterfaceName); if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName); - success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal); - mDhcpInfo = dhcpInfoInternal; + success = NetworkUtils.runDhcp(mInterfaceName, dhcpResults); } else if (dhcpAction == DhcpAction.RENEW) { if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName); - success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal); - dhcpInfoInternal.updateFromDhcpRequest(mDhcpInfo); + success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpResults); + dhcpResults.updateFromDhcpRequest(mDhcpResults); } - if (success) { if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName); - long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion + long leaseDuration = dhcpResults.leaseDuration; //int to long conversion //Sanity check for renewal if (leaseDuration >= 0) { @@ -384,7 +382,8 @@ public class DhcpStateMachine extends StateMachine { //infinite lease time, no renewal needed } - mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal) + mDhcpResults = dhcpResults; + mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults) .sendToTarget(); } else { Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " + diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index 37601fc..8947162 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -170,13 +170,12 @@ public class EthernetDataTracker implements NetworkStateTracker { private void runDhcp() { Thread dhcpThread = new Thread(new Runnable() { public void run() { - DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal(); - if (!NetworkUtils.runDhcp(mIface, dhcpInfoInternal)) { + DhcpResults dhcpResults = new DhcpResults(); + if (!NetworkUtils.runDhcp(mIface, dhcpResults)) { Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError()); return; } - mLinkProperties = dhcpInfoInternal.makeLinkProperties(); - mLinkProperties.setInterfaceName(mIface); + mLinkProperties = dhcpResults.linkProperties; mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr); Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 75646fd..b9362da 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -51,9 +51,10 @@ import java.util.Collections; */ public class LinkProperties implements Parcelable { - String mIfaceName; + private String mIfaceName; private Collection<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>(); private Collection<InetAddress> mDnses = new ArrayList<InetAddress>(); + private String mDomains; private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); private ProxyProperties mHttpProxy; @@ -82,9 +83,10 @@ public class LinkProperties implements Parcelable { mIfaceName = source.getInterfaceName(); for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l); for (InetAddress i : source.getDnses()) mDnses.add(i); + mDomains = source.getDomains(); for (RouteInfo r : source.getRoutes()) mRoutes.add(r); mHttpProxy = (source.getHttpProxy() == null) ? - null : new ProxyProperties(source.getHttpProxy()); + null : new ProxyProperties(source.getHttpProxy()); } } @@ -120,6 +122,14 @@ public class LinkProperties implements Parcelable { return Collections.unmodifiableCollection(mDnses); } + public String getDomains() { + return mDomains; + } + + public void setDomains(String domains) { + mDomains = domains; + } + public void addRoute(RouteInfo route) { if (route != null) mRoutes.add(route); } @@ -138,6 +148,7 @@ public class LinkProperties implements Parcelable { mIfaceName = null; mLinkAddresses.clear(); mDnses.clear(); + mDomains = null; mRoutes.clear(); mHttpProxy = null; } @@ -162,12 +173,14 @@ public class LinkProperties implements Parcelable { for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ","; dns += "] "; - String routes = "Routes: ["; + String domainName = "Domains: " + mDomains; + + String routes = " Routes: ["; for (RouteInfo route : mRoutes) routes += route.toString() + ","; routes += "] "; String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " "); - return ifaceName + linkAddresses + routes + dns + proxy; + return ifaceName + linkAddresses + routes + dns + domainName + proxy; } /** @@ -181,7 +194,7 @@ public class LinkProperties implements Parcelable { } /** - * Compares this {@code LinkProperties} interface name against the target + * Compares this {@code LinkProperties} interface addresses against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. @@ -201,6 +214,12 @@ public class LinkProperties implements Parcelable { */ public boolean isIdenticalDnses(LinkProperties target) { Collection<InetAddress> targetDnses = target.getDnses(); + String targetDomains = target.getDomains(); + if (mDomains == null) { + if (targetDomains != null) return false; + } else { + if (mDomains.equals(targetDomains) == false) return false; + } return (mDnses.size() == targetDnses.size()) ? mDnses.containsAll(targetDnses) : false; } @@ -359,13 +378,13 @@ public class LinkProperties implements Parcelable { return ((null == mIfaceName) ? 0 : mIfaceName.hashCode() + mLinkAddresses.size() * 31 + mDnses.size() * 37 + + ((null == mDomains) ? 0 : mDomains.hashCode()) + mRoutes.size() * 41 + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())); } /** * Implement the Parcelable interface. - * @hide */ public void writeToParcel(Parcel dest, int flags) { dest.writeString(getInterfaceName()); @@ -378,6 +397,7 @@ public class LinkProperties implements Parcelable { for(InetAddress d : mDnses) { dest.writeByteArray(d.getAddress()); } + dest.writeString(mDomains); dest.writeInt(mRoutes.size()); for(RouteInfo route : mRoutes) { @@ -394,19 +414,15 @@ public class LinkProperties implements Parcelable { /** * Implement the Parcelable interface. - * @hide */ public static final Creator<LinkProperties> CREATOR = new Creator<LinkProperties>() { public LinkProperties createFromParcel(Parcel in) { LinkProperties netProp = new LinkProperties(); + String iface = in.readString(); if (iface != null) { - try { - netProp.setInterfaceName(iface); - } catch (Exception e) { - return null; - } + netProp.setInterfaceName(iface); } int addressCount = in.readInt(); for (int i=0; i<addressCount; i++) { @@ -418,6 +434,7 @@ public class LinkProperties implements Parcelable { netProp.addDns(InetAddress.getByAddress(in.createByteArray())); } catch (UnknownHostException e) { } } + netProp.setDomains(in.readString()); addressCount = in.readInt(); for (int i=0; i<addressCount; i++) { netProp.addRoute((RouteInfo)in.readParcelable(null)); diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index d39e741..4ab479e 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -62,21 +62,21 @@ public class NetworkUtils { * addresses. This call blocks until it obtains a result (either success * or failure) from the daemon. * @param interfaceName the name of the interface to configure - * @param ipInfo if the request succeeds, this object is filled in with + * @param dhcpResults if the request succeeds, this object is filled in with * the IP address information. * @return {@code true} for success, {@code false} for failure */ - public native static boolean runDhcp(String interfaceName, DhcpInfoInternal ipInfo); + public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults); /** * Initiate renewal on the Dhcp client daemon. This call blocks until it obtains * a result (either success or failure) from the daemon. * @param interfaceName the name of the interface to configure - * @param ipInfo if the request succeeds, this object is filled in with + * @param dhcpResults if the request succeeds, this object is filled in with * the IP address information. * @return {@code true} for success, {@code false} for failure */ - public native static boolean runDhcpRenew(String interfaceName, DhcpInfoInternal ipInfo); + public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults); /** * Shut down the DHCP client daemon. @@ -124,12 +124,9 @@ public class NetworkUtils { * @param inetAddr is an InetAddress corresponding to the IPv4 address * @return the IP address as an integer in network byte order */ - public static int inetAddressToInt(InetAddress inetAddr) + public static int inetAddressToInt(Inet4Address inetAddr) throws IllegalArgumentException { byte [] addr = inetAddr.getAddress(); - if (addr.length != 4) { - throw new IllegalArgumentException("Not an IPv4 address"); - } return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) | ((addr[1] & 0xff) << 8) | (addr[0] & 0xff); } diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index 275f32a..112e143 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -76,6 +76,10 @@ public class RouteInfo implements Parcelable { this(null, gateway); } + public RouteInfo(LinkAddress host) { + this(host, null); + } + public static RouteInfo makeHostRoute(InetAddress host) { return makeHostRoute(host, null); } diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java index fabe018..04f3974 100644 --- a/core/java/android/net/http/AndroidHttpClient.java +++ b/core/java/android/net/http/AndroidHttpClient.java @@ -17,6 +17,7 @@ package android.net.http; import com.android.internal.http.HttpDateTime; + import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; @@ -25,18 +26,18 @@ import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; -import org.apache.http.entity.AbstractHttpEntity; -import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.protocol.ClientContext; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.params.HttpClientParams; +import org.apache.http.client.protocol.ClientContext; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.entity.AbstractHttpEntity; +import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.RequestWrapper; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; @@ -44,26 +45,26 @@ import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.BasicHttpProcessor; import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.BasicHttpContext; -import java.io.IOException; -import java.io.InputStream; -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; -import java.net.URI; - -import android.content.Context; import android.content.ContentResolver; +import android.content.Context; import android.net.SSLCertificateSocketFactory; import android.net.SSLSessionCache; import android.os.Looper; import android.util.Base64; import android.util.Log; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + /** * Implementation of the Apache {@link DefaultHttpClient} that is configured with * reasonable default settings and registered schemes for Android. @@ -266,7 +267,7 @@ public final class AndroidHttpClient implements HttpClient { return delegate.execute(target, request, context); } - public <T> T execute(HttpUriRequest request, + public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException { return delegate.execute(request, responseHandler); @@ -404,6 +405,11 @@ public final class AndroidHttpClient implements HttpClient { builder.append("curl "); + // add in the method + builder.append("-X "); + builder.append(request.getMethod()); + builder.append(" "); + for (Header header: request.getAllHeaders()) { if (!logAuthToken && (header.getName().equals("Authorization") || diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 2179fa1..80abd0f 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -351,7 +351,7 @@ interface INetworkManagementService /** * Bind name servers to an interface in the DNS resolver. */ - void setDnsServersForInterface(String iface, in String[] servers); + void setDnsServersForInterface(String iface, in String[] servers, String domains); /** * Flush the DNS cache associated with the default interface. diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 0ca9183..27ed6b6 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -31,7 +31,7 @@ import android.util.Log; public final class Trace { private static final String TAG = "Trace"; - // These tags must be kept in sync with frameworks/native/include/utils/Trace.h. + // These tags must be kept in sync with system/core/include/cutils/trace.h. public static final long TRACE_TAG_NEVER = 0; public static final long TRACE_TAG_ALWAYS = 1L << 0; public static final long TRACE_TAG_GRAPHICS = 1L << 1; @@ -44,12 +44,13 @@ public final class Trace { public static final long TRACE_TAG_AUDIO = 1L << 8; public static final long TRACE_TAG_VIDEO = 1L << 9; public static final long TRACE_TAG_CAMERA = 1L << 10; + public static final long TRACE_TAG_HAL = 1L << 11; private static final long TRACE_TAG_NOT_READY = 1L << 63; public static final int TRACE_FLAGS_START_BIT = 1; public static final String[] TRACE_TAGS = { "Graphics", "Input", "View", "WebView", "Window Manager", - "Activity Manager", "Sync Manager", "Audio", "Video", "Camera", + "Activity Manager", "Sync Manager", "Audio", "Video", "Camera", "HAL", }; public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags"; diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 477356e..0f735e8 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -84,7 +84,7 @@ public abstract class WallpaperService extends Service { * tag. */ public static final String SERVICE_META_DATA = "android.service.wallpaper"; - + static final String TAG = "WallpaperService"; static final boolean DEBUG = false; @@ -100,7 +100,6 @@ public abstract class WallpaperService extends Service { private static final int MSG_WINDOW_MOVED = 10035; private static final int MSG_TOUCH_EVENT = 10040; - private Looper mCallbackLooper; private final ArrayList<Engine> mActiveEngines = new ArrayList<Engine>(); @@ -1105,13 +1104,14 @@ public abstract class WallpaperService extends Service { mTarget = context; } + @Override public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight) { new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight); } } - + @Override public void onCreate() { super.onCreate(); @@ -1134,20 +1134,7 @@ public abstract class WallpaperService extends Service { public final IBinder onBind(Intent intent) { return new IWallpaperServiceWrapper(this); } - - /** - * This allows subclasses to change the thread that most callbacks - * occur on. Currently hidden because it is mostly needed for the - * image wallpaper (which runs in the system process and doesn't want - * to get stuck running on that seriously in use main thread). Not - * exposed right now because the semantics of this are not totally - * well defined and some callbacks can still happen on the main thread). - * @hide - */ - public void setCallbackLooper(Looper looper) { - mCallbackLooper = looper; - } - + /** * Must be implemented to return a new instance of the wallpaper's engine. * Note that multiple instances may be active at the same time, such as diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl index ab63187..580d52c 100644 --- a/core/java/android/speech/tts/ITextToSpeechService.aidl +++ b/core/java/android/speech/tts/ITextToSpeechService.aidl @@ -131,6 +131,8 @@ interface ITextToSpeechService { /** * Notifies the engine that it should load a speech synthesis language. * + * @param caller a binder representing the identity of the calling + * TextToSpeech object. * @param lang ISO-3 language code. * @param country ISO-3 country code. May be empty or null. * @param variant Language variant. May be empty or null. @@ -141,13 +143,14 @@ interface ITextToSpeechService { * {@link TextToSpeech#LANG_MISSING_DATA} * {@link TextToSpeech#LANG_NOT_SUPPORTED}. */ - int loadLanguage(in String lang, in String country, in String variant); + int loadLanguage(in IBinder caller, in String lang, in String country, in String variant); /** * Sets the callback that will be notified when playback of utterance from the * given app are completed. * - * @param callingApp Package name for the app whose utterance the callback will handle. + * @param caller Instance a binder representing the identity of the calling + * TextToSpeech object. * @param cb The callback. */ void setCallback(in IBinder caller, ITextToSpeechCallback cb); diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java index d70c371..f98bb09 100644 --- a/core/java/android/speech/tts/SynthesisCallback.java +++ b/core/java/android/speech/tts/SynthesisCallback.java @@ -22,10 +22,11 @@ package android.speech.tts; * {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally * {@link #done}. * - * * {@link #error} can be called at any stage in the synthesis process to * indicate that an error has occurred, but if the call is made after a call * to {@link #done}, it might be discarded. + * + * After {@link #start} been called, {@link #done} must be called regardless of errors. */ public interface SynthesisCallback { /** @@ -72,6 +73,8 @@ public interface SynthesisCallback { * This method should only be called on the synthesis thread, * while in {@link TextToSpeechService#onSynthesizeText}. * + * This method has to be called if {@link #start} was called. + * * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. */ public int done(); diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 5e367cb..cb5e0cd 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.media.AudioManager; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -793,10 +794,16 @@ public class TextToSpeech { } /** - * Speaks the string using the specified queuing strategy and speech - * parameters. + * Speaks the string using the specified queuing strategy and speech parameters. + * This method is asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even started!) at the + * time when this method returns. In order to reliably detect errors during synthesis, + * we recommend setting an utterance progress listener (see + * {@link #setOnUtteranceProgressListener}) and using the + * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter. * - * @param text The string of text to be spoken. + * @param text The string of text to be spoken. No longer than + * {@link #getMaxSpeechInputLength()} characters. * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. * @param params Parameters for the request. Can be null. * Supported parameter names: @@ -809,7 +816,7 @@ public class TextToSpeech { * the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the * engine named "com.svox.pico" if it is being used. * - * @return {@link #ERROR} or {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation. */ public int speak(final String text, final int queueMode, final HashMap<String, String> params) { return runAction(new Action<Integer>() { @@ -830,6 +837,12 @@ public class TextToSpeech { * Plays the earcon using the specified queueing mode and parameters. * The earcon must already have been added with {@link #addEarcon(String, String)} or * {@link #addEarcon(String, String, int)}. + * This method is asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even started!) at the + * time when this method returns. In order to reliably detect errors during synthesis, + * we recommend setting an utterance progress listener (see + * {@link #setOnUtteranceProgressListener}) and using the + * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter. * * @param earcon The earcon that should be played * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. @@ -842,7 +855,7 @@ public class TextToSpeech { * the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the * engine named "com.svox.pico" if it is being used. * - * @return {@link #ERROR} or {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation. */ public int playEarcon(final String earcon, final int queueMode, final HashMap<String, String> params) { @@ -862,6 +875,12 @@ public class TextToSpeech { /** * Plays silence for the specified amount of time using the specified * queue mode. + * This method is asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even started!) at the + * time when this method returns. In order to reliably detect errors during synthesis, + * we recommend setting an utterance progress listener (see + * {@link #setOnUtteranceProgressListener}) and using the + * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter. * * @param durationInMs The duration of the silence. * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. @@ -873,7 +892,7 @@ public class TextToSpeech { * the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the * engine named "com.svox.pico" if it is being used. * - * @return {@link #ERROR} or {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation. */ public int playSilence(final long durationInMs, final int queueMode, final HashMap<String, String> params) { @@ -1031,7 +1050,8 @@ public class TextToSpeech { // the available parts. // Note that the language is not actually set here, instead it is cached so it // will be associated with all upcoming utterances. - int result = service.loadLanguage(language, country, variant); + + int result = service.loadLanguage(getCallerIdentity(), language, country, variant); if (result >= LANG_AVAILABLE){ if (result < LANG_COUNTRY_VAR_AVAILABLE) { variant = ""; @@ -1049,21 +1069,30 @@ public class TextToSpeech { } /** - * Returns a Locale instance describing the language currently being used by the TextToSpeech - * engine. + * Returns a Locale instance describing the language currently being used for synthesis + * requests sent to the TextToSpeech engine. * - * @return language, country (if any) and variant (if any) used by the engine stored in a Locale - * instance, or {@code null} on error. + * In Android 4.2 and before (API <= 17) this function returns the language that is currently + * being used by the TTS engine. That is the last language set by this or any other + * client by a {@link TextToSpeech#setLanguage} call to the same engine. + * + * In Android versions after 4.2 this function returns the language that is currently being + * used for the synthesis requests sent from this client. That is the last language set + * by a {@link TextToSpeech#setLanguage} call on this instance. + * + * @return language, country (if any) and variant (if any) used by the client stored in a + * Locale instance, or {@code null} on error. */ public Locale getLanguage() { return runAction(new Action<Locale>() { @Override - public Locale run(ITextToSpeechService service) throws RemoteException { - String[] locStrings = service.getLanguage(); - if (locStrings != null && locStrings.length == 3) { - return new Locale(locStrings[0], locStrings[1], locStrings[2]); - } - return null; + public Locale run(ITextToSpeechService service) { + /* No service call, but we're accessing mParams, hence need for + wrapping it as an Action instance */ + String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, ""); + String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, ""); + String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, ""); + return new Locale(lang, country, variant); } }, null, "getLanguage"); } @@ -1089,8 +1118,15 @@ public class TextToSpeech { /** * Synthesizes the given text to a file using the specified parameters. + * This method is asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even started!) at the + * time when this method returns. In order to reliably detect errors during synthesis, + * we recommend setting an utterance progress listener (see + * {@link #setOnUtteranceProgressListener}) and using the + * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter. * - * @param text The text that should be synthesized + * @param text The text that should be synthesized. No longer than + * {@link #getMaxSpeechInputLength()} characters. * @param params Parameters for the request. Can be null. * Supported parameter names: * {@link Engine#KEY_PARAM_UTTERANCE_ID}. @@ -1101,7 +1137,7 @@ public class TextToSpeech { * @param filename Absolute file filename to write the generated audio data to.It should be * something like "/sdcard/myappsounds/mysound.wav". * - * @return {@link #ERROR} or {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation. */ public int synthesizeToFile(final String text, final HashMap<String, String> params, final String filename) { @@ -1253,9 +1289,11 @@ public class TextToSpeech { return mEnginesHelper.getEngines(); } - private class Connection implements ServiceConnection { private ITextToSpeechService mService; + + private OnServiceConnectedAsyncTask mOnServiceConnectedAsyncTask; + private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { @Override public void onDone(String utteranceId) { @@ -1282,23 +1320,59 @@ public class TextToSpeech { } }; + private class OnServiceConnectedAsyncTask extends AsyncTask<Void, Void, Integer> { + private final ComponentName mName; + private final ITextToSpeechService mConnectedService; + + public OnServiceConnectedAsyncTask(ComponentName name, IBinder service) { + mName = name; + mConnectedService = ITextToSpeechService.Stub.asInterface(service); + } + + @Override + protected Integer doInBackground(Void... params) { + synchronized(mStartLock) { + if (isCancelled()) { + return null; + } + + try { + mConnectedService.setCallback(getCallerIdentity(), mCallback); + Log.i(TAG, "Setuped connection to " + mName); + return SUCCESS; + } catch (RemoteException re) { + Log.e(TAG, "Error connecting to service, setCallback() failed"); + return ERROR; + } + } + } + + @Override + protected void onPostExecute(Integer result) { + synchronized(mStartLock) { + if (mOnServiceConnectedAsyncTask == this) { + mOnServiceConnectedAsyncTask = null; + } + + mServiceConnection = Connection.this; + mService = mConnectedService; + + dispatchOnInit(result); + } + } + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { - Log.i(TAG, "Connected to " + name); synchronized(mStartLock) { - if (mServiceConnection != null) { - // Disconnect any previous service connection - mServiceConnection.disconnect(); - } - mServiceConnection = this; - mService = ITextToSpeechService.Stub.asInterface(service); - try { - mService.setCallback(getCallerIdentity(), mCallback); - dispatchOnInit(SUCCESS); - } catch (RemoteException re) { - Log.e(TAG, "Error connecting to service, setCallback() failed"); - dispatchOnInit(ERROR); + Log.i(TAG, "Connected to " + name); + + if (mOnServiceConnectedAsyncTask != null) { + mOnServiceConnectedAsyncTask.cancel(false); } + + mOnServiceConnectedAsyncTask = new OnServiceConnectedAsyncTask(name, service); + mOnServiceConnectedAsyncTask.execute(); } } @@ -1306,28 +1380,45 @@ public class TextToSpeech { return mCallback; } - @Override - public void onServiceDisconnected(ComponentName name) { + /** + * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set. + * + * @return true if we cancel mOnServiceConnectedAsyncTask in progress. + */ + private boolean clearServiceConnection() { synchronized(mStartLock) { + boolean result = false; + if (mOnServiceConnectedAsyncTask != null) { + result = mOnServiceConnectedAsyncTask.cancel(false); + mOnServiceConnectedAsyncTask = null; + } + mService = null; // If this is the active connection, clear it if (mServiceConnection == this) { mServiceConnection = null; } + return result; + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.i(TAG, "Asked to disconnect from " + name); + if (clearServiceConnection()) { + /* We need to protect against a rare case where engine + * dies just after successful connection - and we process onServiceDisconnected + * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels + * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit + * with ERROR as argument. + */ + dispatchOnInit(ERROR); } } public void disconnect() { mContext.unbindService(this); - - synchronized (mStartLock) { - mService = null; - // If this is the active connection, clear it - if (mServiceConnection == this) { - mServiceConnection = null; - } - - } + clearServiceConnection(); } public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) { @@ -1394,4 +1485,13 @@ public class TextToSpeech { } + /** + * Limit of length of input string passed to speak and synthesizeToFile. + * + * @see #speak + * @see #synthesizeToFile + */ + public static int getMaxSpeechInputLength() { + return 4000; + } } diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index d124e68..458350d 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -34,7 +34,6 @@ import android.text.TextUtils; import android.util.Log; import java.io.File; -import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Set; @@ -74,7 +73,7 @@ public abstract class TextToSpeechService extends Service { private static final boolean DBG = false; private static final String TAG = "TextToSpeechService"; - private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; + private static final String SYNTH_THREAD_NAME = "SynthThread"; private SynthHandler mSynthHandler; @@ -129,6 +128,8 @@ public abstract class TextToSpeechService extends Service { * * Can be called on multiple threads. * + * Its return values HAVE to be consistent with onLoadLanguage. + * * @param lang ISO-3 language code. * @param country ISO-3 country code. May be empty or null. * @param variant Language variant. May be empty or null. @@ -163,6 +164,8 @@ public abstract class TextToSpeechService extends Service { * at some point in the future. * * Can be called on multiple threads. + * In <= Android 4.2 (<= API 17) can be called on main and service binder threads. + * In > Android 4.2 (> API 17) can be called on main and synthesis threads. * * @param lang ISO-3 language code. * @param country ISO-3 country code. May be empty or null. @@ -256,7 +259,6 @@ public abstract class TextToSpeechService extends Service { } private class SynthHandler extends Handler { - private SpeechItem mCurrentSpeechItem = null; public SynthHandler(Looper looper) { @@ -275,7 +277,7 @@ public abstract class TextToSpeechService extends Service { private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) { if (mCurrentSpeechItem != null && - mCurrentSpeechItem.getCallerIdentity() == callerIdentity) { + (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) { SpeechItem current = mCurrentSpeechItem; mCurrentSpeechItem = null; return current; @@ -296,7 +298,6 @@ public abstract class TextToSpeechService extends Service { if (current != null) { current.stop(); } - // The AudioPlaybackHandler will be destroyed by the caller. } @@ -306,8 +307,15 @@ public abstract class TextToSpeechService extends Service { * Called on a service binder thread. */ public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) { + UtteranceProgressDispatcher utterenceProgress = null; + if (speechItem instanceof UtteranceProgressDispatcher) { + utterenceProgress = (UtteranceProgressDispatcher) speechItem; + } + if (!speechItem.isValid()) { - speechItem.dispatchOnError(); + if (utterenceProgress != null) { + utterenceProgress.dispatchOnError(); + } return TextToSpeech.ERROR; } @@ -325,6 +333,7 @@ public abstract class TextToSpeechService extends Service { } }; Message msg = Message.obtain(this, runnable); + // The obj is used to remove all callbacks from the given app in // stopForApp(String). // @@ -334,7 +343,9 @@ public abstract class TextToSpeechService extends Service { return TextToSpeech.SUCCESS; } else { Log.w(TAG, "SynthThread has quit"); - speechItem.dispatchOnError(); + if (utterenceProgress != null) { + utterenceProgress.dispatchOnError(); + } return TextToSpeech.ERROR; } } @@ -370,7 +381,7 @@ public abstract class TextToSpeechService extends Service { } public int stopAll() { - // Stop the current speech item unconditionally. + // Stop the current speech item unconditionally . SpeechItem current = setCurrentSpeechItem(null); if (current != null) { current.stop(); @@ -393,7 +404,7 @@ public abstract class TextToSpeechService extends Service { /** * An item in the synth thread queue. */ - private abstract class SpeechItem implements UtteranceProgressDispatcher { + private abstract class SpeechItem { private final Object mCallerIdentity; protected final Bundle mParams; private final int mCallerUid; @@ -412,6 +423,15 @@ public abstract class TextToSpeechService extends Service { return mCallerIdentity; } + + public int getCallerUid() { + return mCallerUid; + } + + public int getCallerPid() { + return mCallerPid; + } + /** * Checker whether the item is valid. If this method returns false, the item should not * be played. @@ -436,6 +456,8 @@ public abstract class TextToSpeechService extends Service { return playImpl(); } + protected abstract int playImpl(); + /** * Stops the speech item. * Must not be called more than once. @@ -452,6 +474,23 @@ public abstract class TextToSpeechService extends Service { stopImpl(); } + protected abstract void stopImpl(); + + protected synchronized boolean isStopped() { + return mStopped; + } + } + + /** + * An item in the synth thread queue that process utterance. + */ + private abstract class UtteranceSpeechItem extends SpeechItem + implements UtteranceProgressDispatcher { + + public UtteranceSpeechItem(Object caller, int callerUid, int callerPid, Bundle params) { + super(caller, callerUid, callerPid, params); + } + @Override public void dispatchOnDone() { final String utteranceId = getUtteranceId(); @@ -476,22 +515,6 @@ public abstract class TextToSpeechService extends Service { } } - public int getCallerUid() { - return mCallerUid; - } - - public int getCallerPid() { - return mCallerPid; - } - - protected synchronized boolean isStopped() { - return mStopped; - } - - protected abstract int playImpl(); - - protected abstract void stopImpl(); - public int getStreamType() { return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM); } @@ -519,9 +542,10 @@ public abstract class TextToSpeechService extends Service { protected float getFloatParam(String key, float defaultValue) { return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue); } + } - class SynthesisSpeechItem extends SpeechItem { + class SynthesisSpeechItem extends UtteranceSpeechItem { // Never null. private final String mText; private final SynthesisRequest mSynthesisRequest; @@ -552,7 +576,7 @@ public abstract class TextToSpeechService extends Service { Log.e(TAG, "null synthesis text"); return false; } - if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) { + if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) { Log.w(TAG, "Text too long: " + mText.length() + " chars"); return false; } @@ -658,7 +682,7 @@ public abstract class TextToSpeechService extends Service { } } - private class AudioSpeechItem extends SpeechItem { + private class AudioSpeechItem extends UtteranceSpeechItem { private final AudioPlaybackQueueItem mItem; public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid, Bundle params, Uri uri) { @@ -684,7 +708,7 @@ public abstract class TextToSpeechService extends Service { } } - private class SilenceSpeechItem extends SpeechItem { + private class SilenceSpeechItem extends UtteranceSpeechItem { private final long mDuration; public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid, @@ -711,6 +735,41 @@ public abstract class TextToSpeechService extends Service { } } + private class LoadLanguageItem extends SpeechItem { + private final String mLanguage; + private final String mCountry; + private final String mVariant; + + public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid, + Bundle params, String language, String country, String variant) { + super(callerIdentity, callerUid, callerPid, params); + mLanguage = language; + mCountry = country; + mVariant = variant; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + protected int playImpl() { + int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant); + if (result == TextToSpeech.LANG_AVAILABLE || + result == TextToSpeech.LANG_COUNTRY_AVAILABLE || + result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) { + return TextToSpeech.SUCCESS; + } + return TextToSpeech.ERROR; + } + + @Override + protected void stopImpl() { + // No-op + } + } + @Override public IBinder onBind(Intent intent) { if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) { @@ -822,12 +881,25 @@ public abstract class TextToSpeechService extends Service { * are enforced. */ @Override - public int loadLanguage(String lang, String country, String variant) { + public int loadLanguage(IBinder caller, String lang, String country, String variant) { if (!checkNonNull(lang)) { return TextToSpeech.ERROR; } + int retVal = onIsLanguageAvailable(lang, country, variant); + + if (retVal == TextToSpeech.LANG_AVAILABLE || + retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE || + retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) { - return onLoadLanguage(lang, country, variant); + SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(), + Binder.getCallingPid(), null, lang, country, variant); + + if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) != + TextToSpeech.SUCCESS) { + return TextToSpeech.ERROR; + } + } + return retVal; } @Override diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 1aab911..160c630 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -16,6 +16,7 @@ package android.text; +import android.graphics.Color; import com.android.internal.util.ArrayUtils; import org.ccil.cowan.tagsoup.HTMLSchema; import org.ccil.cowan.tagsoup.Parser; @@ -168,7 +169,7 @@ public class Html { for(int j = 0; j < style.length; j++) { if (style[j] instanceof AlignmentSpan) { - Layout.Alignment align = + Layout.Alignment align = ((AlignmentSpan) style[j]).getAlignment(); needDiv = true; if (align == Layout.Alignment.ALIGN_CENTER) { @@ -181,7 +182,7 @@ public class Html { } } if (needDiv) { - out.append("<div " + elements + ">"); + out.append("<div ").append(elements).append(">"); } withinDiv(out, text, i, next); @@ -199,13 +200,13 @@ public class Html { next = text.nextSpanTransition(i, end, QuoteSpan.class); QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class); - for (QuoteSpan quote: quotes) { + for (QuoteSpan quote : quotes) { out.append("<blockquote>"); } withinBlockquote(out, text, i, next); - for (QuoteSpan quote: quotes) { + for (QuoteSpan quote : quotes) { out.append("</blockquote>\n"); } } @@ -391,7 +392,7 @@ public class Html { } else if (c == '&') { out.append("&"); } else if (c > 0x7E || c < ' ') { - out.append("&#" + ((int) c) + ";"); + out.append("&#").append((int) c).append(";"); } else if (c == ' ') { while (i + 1 < end && text.charAt(i + 1) == ' ') { out.append(" "); @@ -616,8 +617,6 @@ class HtmlToSpannedConverter implements ContentHandler { if (where != len) { text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } - - return; } private static void startImg(SpannableStringBuilder text, @@ -673,7 +672,7 @@ class HtmlToSpannedConverter implements ContentHandler { Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } else { - int c = getHtmlColor(f.mColor); + int c = Color.getHtmlColor(f.mColor); if (c != -1) { text.setSpan(new ForegroundColorSpan(c | 0xFF000000), where, len, @@ -842,47 +841,4 @@ class HtmlToSpannedConverter implements ContentHandler { mLevel = level; } } - - private static HashMap<String,Integer> COLORS = buildColorMap(); - - private static HashMap<String,Integer> buildColorMap() { - HashMap<String,Integer> map = new HashMap<String,Integer>(); - map.put("aqua", 0x00FFFF); - map.put("black", 0x000000); - map.put("blue", 0x0000FF); - map.put("fuchsia", 0xFF00FF); - map.put("green", 0x008000); - map.put("grey", 0x808080); - map.put("lime", 0x00FF00); - map.put("maroon", 0x800000); - map.put("navy", 0x000080); - map.put("olive", 0x808000); - map.put("purple", 0x800080); - map.put("red", 0xFF0000); - map.put("silver", 0xC0C0C0); - map.put("teal", 0x008080); - map.put("white", 0xFFFFFF); - map.put("yellow", 0xFFFF00); - return map; - } - - /** - * Converts an HTML color (named or numeric) to an integer RGB value. - * - * @param color Non-null color string. - * @return A color value, or {@code -1} if the color string could not be interpreted. - */ - private static int getHtmlColor(String color) { - Integer i = COLORS.get(color.toLowerCase()); - if (i != null) { - return i; - } else { - try { - return XmlUtils.convertValueToInt(color, -1); - } catch (NumberFormatException nfe) { - return -1; - } - } - } - } diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java index 3d9daed..c95df46 100644 --- a/core/java/android/text/method/DigitsKeyListener.java +++ b/core/java/android/text/method/DigitsKeyListener.java @@ -49,13 +49,22 @@ public class DigitsKeyListener extends NumberKeyListener * @see KeyEvent#getMatch * @see #getAcceptedChars */ - private static final char[][] CHARACTERS = new char[][] { - new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, - new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' }, - new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' }, - new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' }, + private static final char[][] CHARACTERS = { + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' }, + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' }, + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' }, }; + private static boolean isSignChar(final char c) { + return c == '-' || c == '+'; + } + + // TODO: Needs internationalization + private static boolean isDecimalPointChar(final char c) { + return c == '.'; + } + /** * Allocates a DigitsKeyListener that accepts the digits 0 through 9. */ @@ -145,32 +154,32 @@ public class DigitsKeyListener extends NumberKeyListener int dlen = dest.length(); /* - * Find out if the existing text has '-' or '.' characters. + * Find out if the existing text has a sign or decimal point characters. */ for (int i = 0; i < dstart; i++) { char c = dest.charAt(i); - if (c == '-') { + if (isSignChar(c)) { sign = i; - } else if (c == '.') { + } else if (isDecimalPointChar(c)) { decimal = i; } } for (int i = dend; i < dlen; i++) { char c = dest.charAt(i); - if (c == '-') { - return ""; // Nothing can be inserted in front of a '-'. - } else if (c == '.') { + if (isSignChar(c)) { + return ""; // Nothing can be inserted in front of a sign character. + } else if (isDecimalPointChar(c)) { decimal = i; } } /* * If it does, we must strip them out from the source. - * In addition, '-' must be the very first character, - * and nothing can be inserted before an existing '-'. + * In addition, a sign character must be the very first character, + * and nothing can be inserted before an existing sign character. * Go in reverse order so the offsets are stable. */ @@ -180,7 +189,7 @@ public class DigitsKeyListener extends NumberKeyListener char c = source.charAt(i); boolean strip = false; - if (c == '-') { + if (isSignChar(c)) { if (i != start || dstart != 0) { strip = true; } else if (sign >= 0) { @@ -188,7 +197,7 @@ public class DigitsKeyListener extends NumberKeyListener } else { sign = i; } - } else if (c == '.') { + } else if (isDecimalPointChar(c)) { if (decimal >= 0) { strip = true; } else { diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 5dc206f..0ec7e84 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -17,6 +17,7 @@ package android.text.style; import android.content.Context; +import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Parcel; @@ -26,6 +27,7 @@ import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; import android.util.Log; +import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import java.util.Arrays; @@ -45,6 +47,8 @@ import java.util.Locale; */ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { + private static final String TAG = "SuggestionSpan"; + /** * Sets this flag if the suggestions should be easily accessible with few interactions. * This flag should be set for every suggestions that the user is likely to use. @@ -82,6 +86,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { private final String[] mSuggestions; private final String mLocaleString; private final String mNotificationTargetClassName; + private final String mNotificationTargetPackageName; private final int mHashCode; private float mEasyCorrectUnderlineThickness; @@ -134,6 +139,12 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { mLocaleString = ""; } + if (context != null) { + mNotificationTargetPackageName = context.getPackageName(); + } else { + mNotificationTargetPackageName = null; + } + if (notificationTargetClass != null) { mNotificationTargetClassName = notificationTargetClass.getCanonicalName(); } else { @@ -185,6 +196,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { mFlags = src.readInt(); mLocaleString = src.readString(); mNotificationTargetClassName = src.readString(); + mNotificationTargetPackageName = src.readString(); mHashCode = src.readInt(); mEasyCorrectUnderlineColor = src.readInt(); mEasyCorrectUnderlineThickness = src.readFloat(); @@ -240,6 +252,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { dest.writeInt(mFlags); dest.writeString(mLocaleString); dest.writeString(mNotificationTargetClassName); + dest.writeString(mNotificationTargetPackageName); dest.writeInt(mHashCode); dest.writeInt(mEasyCorrectUnderlineColor); dest.writeFloat(mEasyCorrectUnderlineThickness); @@ -325,4 +338,40 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { } return 0; } + + /** + * Notifies a suggestion selection. + * + * @hide + */ + public void notifySelection(Context context, String original, int index) { + final Intent intent = new Intent(); + + if (context == null || mNotificationTargetClassName == null) { + return; + } + // Ensures that only a class in the original IME package will receive the + // notification. + if (mSuggestions == null || index < 0 || index >= mSuggestions.length) { + Log.w(TAG, "Unable to notify the suggestion as the index is out of range index=" + index + + " length=" + mSuggestions.length); + return; + } + + // The package name is not mandatory (legacy from JB), and if the package name + // is missing, we try to notify the suggestion through the input method manager. + if (mNotificationTargetPackageName != null) { + intent.setClassName(mNotificationTargetPackageName, mNotificationTargetClassName); + intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, original); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, mSuggestions[index]); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, hashCode()); + context.sendBroadcast(intent); + } else { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.notifySuggestionPicked(this, original, index); + } + } + } } diff --git a/core/java/android/util/FinitePool.java b/core/java/android/util/FinitePool.java deleted file mode 100644 index b30f2bf..0000000 --- a/core/java/android/util/FinitePool.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.util; - -/** - * @hide - */ -class FinitePool<T extends Poolable<T>> implements Pool<T> { - private static final String LOG_TAG = "FinitePool"; - - /** - * Factory used to create new pool objects - */ - private final PoolableManager<T> mManager; - /** - * Maximum number of objects in the pool - */ - private final int mLimit; - /** - * If true, mLimit is ignored - */ - private final boolean mInfinite; - - /** - * Next object to acquire - */ - private T mRoot; - /** - * Number of objects in the pool - */ - private int mPoolCount; - - FinitePool(PoolableManager<T> manager) { - mManager = manager; - mLimit = 0; - mInfinite = true; - } - - FinitePool(PoolableManager<T> manager, int limit) { - if (limit <= 0) throw new IllegalArgumentException("The pool limit must be > 0"); - - mManager = manager; - mLimit = limit; - mInfinite = false; - } - - public T acquire() { - T element; - - if (mRoot != null) { - element = mRoot; - mRoot = element.getNextPoolable(); - mPoolCount--; - } else { - element = mManager.newInstance(); - } - - if (element != null) { - element.setNextPoolable(null); - element.setPooled(false); - mManager.onAcquired(element); - } - - return element; - } - - public void release(T element) { - if (!element.isPooled()) { - if (mInfinite || mPoolCount < mLimit) { - mPoolCount++; - element.setNextPoolable(mRoot); - element.setPooled(true); - mRoot = element; - } - mManager.onReleased(element); - } else { - Log.w(LOG_TAG, "Element is already in pool: " + element); - } - } -} diff --git a/core/java/android/util/Poolable.java b/core/java/android/util/Poolable.java deleted file mode 100644 index 87e0529..0000000 --- a/core/java/android/util/Poolable.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.util; - -/** - * @hide - */ -public interface Poolable<T> { - void setNextPoolable(T element); - T getNextPoolable(); - boolean isPooled(); - void setPooled(boolean isPooled); -} diff --git a/core/java/android/util/PoolableManager.java b/core/java/android/util/PoolableManager.java deleted file mode 100644 index 8773e63..0000000 --- a/core/java/android/util/PoolableManager.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.util; - -/** - * @hide - */ -public interface PoolableManager<T extends Poolable<T>> { - T newInstance(); - - void onAcquired(T element); - void onReleased(T element); -} diff --git a/core/java/android/util/Pools.java b/core/java/android/util/Pools.java index 8edb3e6..9f5f36e 100644 --- a/core/java/android/util/Pools.java +++ b/core/java/android/util/Pools.java @@ -17,25 +17,169 @@ package android.util; /** + * Helper class for crating pools of objects. An example use looks like this: + * <pre> + * public class MyPooledClass { + * + * private static final SynchronizedPool<MyPooledClass> sPool = + * new SynchronizedPool<MyPooledClass>(10); + * + * public static MyPooledClass obtain() { + * MyPooledClass instance = sPool.acquire(); + * return (instance != null) ? instance : new MyPooledClass(); + * } + * + * public void recycle() { + * // Clear state if needed. + * sPool.release(this); + * } + * + * . . . + * } + * </pre> + * * @hide */ -public class Pools { - private Pools() { +public final class Pools { + + /** + * Interface for managing a pool of objects. + * + * @param <T> The pooled type. + */ + public static interface Pool<T> { + + /** + * @return An instance from the pool if such, null otherwise. + */ + public T acquire(); + + /** + * Release an instance to the pool. + * + * @param instance The instance to release. + * @return Whether the instance was put in the pool. + * + * @throws IllegalStateException If the instance is already in the pool. + */ + public boolean release(T instance); } - public static <T extends Poolable<T>> Pool<T> simplePool(PoolableManager<T> manager) { - return new FinitePool<T>(manager); + private Pools() { + /* do nothing - hiding constructor */ } - - public static <T extends Poolable<T>> Pool<T> finitePool(PoolableManager<T> manager, int limit) { - return new FinitePool<T>(manager, limit); + + private static class PoolableHolder<T> { + T mPoolable; + PoolableHolder<T> mNext; } - public static <T extends Poolable<T>> Pool<T> synchronizedPool(Pool<T> pool) { - return new SynchronizedPool<T>(pool); + /** + * Simple (non-synchronized) pool of objects. + * + * @param <T> The pooled type. + */ + public static class SimplePool<T> implements Pool<T> { + private final int mMaxPoolSize; + + private int mPoolSize; + + private PoolableHolder<T> mEmptyHolders; + private PoolableHolder<T> mPool; + + /** + * Creates a new instance. + * + * @param maxPoolSize The max pool size. + * + * @throws IllegalArgumentException If the max pool size is less than zero. + */ + public SimplePool(int maxPoolSize) { + if (maxPoolSize <= 0) { + throw new IllegalArgumentException("The max pool size must be > 0"); + } + mMaxPoolSize = maxPoolSize; + } + + @Override + public T acquire() { + if (mPool != null) { + PoolableHolder<T> holder = mPool; + mPool = holder.mNext; + T poolable = holder.mPoolable; + holder.mPoolable = null; + holder.mNext = mEmptyHolders; + mEmptyHolders = holder; + mPoolSize--; + return poolable; + } + return null; + } + + @Override + public boolean release(T instance) { + if (isInPool(instance)) { + throw new IllegalStateException("Already in the pool!"); + } + if (mPoolSize < mMaxPoolSize) { + PoolableHolder<T> holder = mEmptyHolders; + if (holder == null) { + holder = new PoolableHolder<T>(); + } else { + mEmptyHolders = holder.mNext; + } + holder.mPoolable = instance; + holder.mNext = mPool; + mPool = holder; + mPoolSize++; + return true; + } + return false; + } + + private boolean isInPool(T instance) { + PoolableHolder<T> current = mPool; + while (current != null) { + if (current.mPoolable == instance) { + return true; + } + current = current.mNext; + } + return false; + } } - public static <T extends Poolable<T>> Pool<T> synchronizedPool(Pool<T> pool, Object lock) { - return new SynchronizedPool<T>(pool, lock); + /** + * Synchronized) pool of objects. + * + * @param <T> The pooled type. + */ + public static class SynchronizedPool<T> extends SimplePool<T> { + private final Object mLock = new Object(); + + /** + * Creates a new instance. + * + * @param maxPoolSize The max pool size. + * + * @throws IllegalArgumentException If the max pool size is less than zero. + */ + public SynchronizedPool(int maxPoolSize) { + super(maxPoolSize); + } + + @Override + public T acquire() { + synchronized (mLock) { + return super.acquire(); + } + } + + @Override + public boolean release(T element) { + synchronized (mLock) { + return super.release(element); + } + } } } diff --git a/core/java/android/util/PropertyValueModel.java b/core/java/android/util/PropertyValueModel.java new file mode 100755 index 0000000..eb9c47d --- /dev/null +++ b/core/java/android/util/PropertyValueModel.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * A value model for a {@link Property property} of a host object. This class can be used for + * both reflective and non-reflective property implementations. + * + * @param <H> the host type, where the host is the object that holds this property + * @param <T> the value type + * + * @see Property + * @see ValueModel + */ +public class PropertyValueModel<H, T> extends ValueModel<T> { + private final H mHost; + private final Property<H, T> mProperty; + + private PropertyValueModel(H host, Property<H, T> property) { + mProperty = property; + mHost = host; + } + + /** + * Returns the host. + * + * @return the host + */ + public H getHost() { + return mHost; + } + + /** + * Returns the property. + * + * @return the property + */ + public Property<H, T> getProperty() { + return mProperty; + } + + @Override + public Class<T> getType() { + return mProperty.getType(); + } + + @Override + public T get() { + return mProperty.get(mHost); + } + + @Override + public void set(T value) { + mProperty.set(mHost, value); + } + + /** + * Return an appropriate PropertyValueModel for this host and property. + * + * @param host the host + * @param property the property + * @return the value model + */ + public static <H, T> PropertyValueModel<H, T> of(H host, Property<H, T> property) { + return new PropertyValueModel<H, T>(host, property); + } + + /** + * Return a PropertyValueModel for this {@code host} and a + * reflective property, constructed from this {@code propertyType} and {@code propertyName}. + * + * @param host + * @param propertyType the property type + * @param propertyName the property name + * @return a value model with this host and a reflective property with this type and name + * + * @see Property#of + */ + public static <H, T> PropertyValueModel<H, T> of(H host, Class<T> propertyType, + String propertyName) { + return of(host, Property.of((Class<H>) host.getClass(), propertyType, propertyName)); + } + + private static Class getNullaryMethodReturnType(Class c, String name) { + try { + return c.getMethod(name).getReturnType(); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static Class getFieldType(Class c, String name) { + try { + return c.getField(name).getType(); + } catch (NoSuchFieldException e) { + return null; + } + } + + private static String capitalize(String name) { + if (name.isEmpty()) { + return name; + } + return Character.toUpperCase(name.charAt(0)) + name.substring(1); + } + + /** + * Return a PropertyValueModel for this {@code host} and and {@code propertyName}. + * + * @param host the host + * @param propertyName the property name + * @return a value model with this host and a reflective property with this name + */ + public static PropertyValueModel of(Object host, String propertyName) { + Class clazz = host.getClass(); + String suffix = capitalize(propertyName); + Class propertyType = getNullaryMethodReturnType(clazz, "get" + suffix); + if (propertyType == null) { + propertyType = getNullaryMethodReturnType(clazz, "is" + suffix); + } + if (propertyType == null) { + propertyType = getFieldType(clazz, propertyName); + } + if (propertyType == null) { + throw new NoSuchPropertyException(propertyName); + } + return of(host, propertyType, propertyName); + } +} diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java index a08d5cb..2f7a6fe 100644 --- a/core/java/android/util/SparseLongArray.java +++ b/core/java/android/util/SparseLongArray.java @@ -22,8 +22,6 @@ import com.android.internal.util.ArrayUtils; * SparseLongArrays map integers to longs. Unlike a normal array of longs, * there can be gaps in the indices. It is intended to be more efficient * than using a HashMap to map Integers to Longs. - * - * @hide */ public class SparseLongArray implements Cloneable { diff --git a/core/java/android/util/SynchronizedPool.java b/core/java/android/util/SynchronizedPool.java deleted file mode 100644 index 651e0c3..0000000 --- a/core/java/android/util/SynchronizedPool.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.util; - -/** - * - * @hide - */ -class SynchronizedPool<T extends Poolable<T>> implements Pool<T> { - private final Pool<T> mPool; - private final Object mLock; - - public SynchronizedPool(Pool<T> pool) { - mPool = pool; - mLock = this; - } - - public SynchronizedPool(Pool<T> pool, Object lock) { - mPool = pool; - mLock = lock; - } - - public T acquire() { - synchronized (mLock) { - return mPool.acquire(); - } - } - - public void release(T element) { - synchronized (mLock) { - mPool.release(element); - } - } -} diff --git a/core/java/android/util/ValueModel.java b/core/java/android/util/ValueModel.java new file mode 100755 index 0000000..4789682 --- /dev/null +++ b/core/java/android/util/ValueModel.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * A ValueModel is an abstraction for a 'slot' or place in memory in which a value + * may be stored and retrieved. A common implementation of ValueModel is a regular property of + * an object, whose value may be retrieved by calling the appropriate <em>getter</em> + * method and set by calling the corresponding <em>setter</em> method. + * + * @param <T> the value type + * + * @see PropertyValueModel + */ +public abstract class ValueModel<T> { + /** + * The empty model should be used in place of {@code null} to indicate that a + * model has not been set. The empty model has no value and does nothing when it is set. + */ + public static final ValueModel EMPTY = new ValueModel() { + @Override + public Class getType() { + return Object.class; + } + + @Override + public Object get() { + return null; + } + + @Override + public void set(Object value) { + + } + }; + + protected ValueModel() { + } + + /** + * Returns the type of this property. + * + * @return the property type + */ + public abstract Class<T> getType(); + + /** + * Returns the value of this property. + * + * @return the property value + */ + public abstract T get(); + + /** + * Sets the value of this property. + * + * @param value the new value for this property + */ + public abstract void set(T value); +}
\ No newline at end of file diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 9bee4bf..ba82d79 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -18,6 +18,7 @@ package android.view; import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS; +import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; @@ -26,6 +27,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.util.SparseLongArray; +import android.view.View.AttachInfo; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; @@ -62,7 +64,10 @@ final class AccessibilityInteractionController { private final ArrayList<View> mTempArrayList = new ArrayList<View>(); + private final Point mTempPoint = new Point(); private final Rect mTempRect = new Rect(); + private final Rect mTempRect1 = new Rect(); + private final Rect mTempRect2 = new Rect(); public AccessibilityInteractionController(ViewRootImpl viewRootImpl) { Looper looper = viewRootImpl.mHandler.getLooper(); @@ -86,7 +91,7 @@ final class AccessibilityInteractionController { public void findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid) { + long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID; message.arg1 = flags; @@ -96,6 +101,7 @@ final class AccessibilityInteractionController { args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); args.argi3 = interactionId; args.arg1 = callback; + args.arg2 = spec; message.obj = args; // If the interrogation is performed by the same thread as the main UI @@ -119,6 +125,7 @@ final class AccessibilityInteractionController { final int interactionId = args.argi3; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; + final MagnificationSpec spec = (MagnificationSpec) args.arg2; args.recycle(); @@ -142,7 +149,10 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; - applyApplicationScaleIfNeeded(infos); + applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); + if (spec != null) { + spec.recycle(); + } callback.setFindAccessibilityNodeInfosResult(infos, interactionId); infos.clear(); } catch (RemoteException re) { @@ -153,7 +163,7 @@ final class AccessibilityInteractionController { public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId, int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interrogatingPid, long interrogatingTid) { + int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID; message.arg1 = flags; @@ -163,6 +173,7 @@ final class AccessibilityInteractionController { args.argi1 = viewId; args.argi2 = interactionId; args.arg1 = callback; + args.arg2 = spec; message.obj = args; @@ -187,6 +198,7 @@ final class AccessibilityInteractionController { final int interactionId = args.argi2; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; + final MagnificationSpec spec = (MagnificationSpec) args.arg2; args.recycle(); @@ -212,7 +224,10 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; - applyApplicationScaleIfNeeded(info); + applyAppScaleAndMagnificationSpecIfNeeded(info, spec); + if (spec != null) { + spec.recycle(); + } callback.setFindAccessibilityNodeInfoResult(info, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -222,7 +237,7 @@ final class AccessibilityInteractionController { public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interrogatingPid, long interrogatingTid) { + int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT; message.arg1 = flags; @@ -230,10 +245,10 @@ final class AccessibilityInteractionController { SomeArgs args = SomeArgs.obtain(); args.arg1 = text; args.arg2 = callback; + args.arg3 = spec; args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); args.argi3 = interactionId; - message.obj = args; // If the interrogation is performed by the same thread as the main UI @@ -255,6 +270,7 @@ final class AccessibilityInteractionController { final String text = (String) args.arg1; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg2; + final MagnificationSpec spec = (MagnificationSpec) args.arg3; final int accessibilityViewId = args.argi1; final int virtualDescendantId = args.argi2; final int interactionId = args.argi3; @@ -310,7 +326,10 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; - applyApplicationScaleIfNeeded(infos); + applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); + if (spec != null) { + spec.recycle(); + } callback.setFindAccessibilityNodeInfosResult(infos, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -320,7 +339,7 @@ final class AccessibilityInteractionController { public void findFocusClientThread(long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, - long interrogatingTid) { + long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_FOCUS; message.arg1 = flags; @@ -331,6 +350,7 @@ final class AccessibilityInteractionController { args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); args.arg1 = callback; + args.arg2 = spec; message.obj = args; @@ -356,7 +376,7 @@ final class AccessibilityInteractionController { final int virtualDescendantId = args.argi3; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; - + final MagnificationSpec spec = (MagnificationSpec) args.arg2; args.recycle(); AccessibilityNodeInfo focused = null; @@ -407,7 +427,10 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; - applyApplicationScaleIfNeeded(focused); + applyAppScaleAndMagnificationSpecIfNeeded(focused, spec); + if (spec != null) { + spec.recycle(); + } callback.setFindAccessibilityNodeInfoResult(focused, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -417,7 +440,7 @@ final class AccessibilityInteractionController { public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, - long interrogatingTid) { + long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FOCUS_SEARCH; message.arg1 = flags; @@ -427,6 +450,7 @@ final class AccessibilityInteractionController { args.argi2 = direction; args.argi3 = interactionId; args.arg1 = callback; + args.arg2 = spec; message.obj = args; @@ -451,6 +475,7 @@ final class AccessibilityInteractionController { final int interactionId = args.argi3; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; + final MagnificationSpec spec = (MagnificationSpec) args.arg2; args.recycle(); @@ -476,7 +501,10 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; - applyApplicationScaleIfNeeded(next); + applyAppScaleAndMagnificationSpecIfNeeded(next, spec); + if (spec != null) { + spec.recycle(); + } callback.setFindAccessibilityNodeInfoResult(next, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -572,38 +600,84 @@ final class AccessibilityInteractionController { return foundView; } - private void applyApplicationScaleIfNeeded(List<AccessibilityNodeInfo> infos) { + private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, + MagnificationSpec spec) { if (infos == null) { return; } final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; - if (applicationScale != 1.0f) { + if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { final int infoCount = infos.size(); for (int i = 0; i < infoCount; i++) { AccessibilityNodeInfo info = infos.get(i); - applyApplicationScaleIfNeeded(info); + applyAppScaleAndMagnificationSpecIfNeeded(info, spec); } } } - private void applyApplicationScaleIfNeeded(AccessibilityNodeInfo info) { + private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, + MagnificationSpec spec) { if (info == null) { return; } + final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; + if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { + return; + } + + Rect boundsInParent = mTempRect; + Rect boundsInScreen = mTempRect1; + + info.getBoundsInParent(boundsInParent); + info.getBoundsInScreen(boundsInScreen); if (applicationScale != 1.0f) { - Rect bounds = mTempRect; + boundsInParent.scale(applicationScale); + boundsInScreen.scale(applicationScale); + } + if (spec != null) { + boundsInParent.scale(spec.scale); + // boundsInParent must not be offset. + boundsInScreen.scale(spec.scale); + boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY); + } + info.setBoundsInParent(boundsInParent); + info.setBoundsInScreen(boundsInScreen); + + if (spec != null) { + AttachInfo attachInfo = mViewRootImpl.mAttachInfo; + if (attachInfo.mDisplay == null) { + return; + } - info.getBoundsInParent(bounds); - bounds.scale(applicationScale); - info.setBoundsInParent(bounds); + final float scale = attachInfo.mApplicationScale * spec.scale; - info.getBoundsInScreen(bounds); - bounds.scale(applicationScale); - info.setBoundsInScreen(bounds); + Rect visibleWinFrame = mTempRect1; + visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX); + visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY); + visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale); + visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale); + + attachInfo.mDisplay.getRealSize(mTempPoint); + final int displayWidth = mTempPoint.x; + final int displayHeight = mTempPoint.y; + + Rect visibleDisplayFrame = mTempRect2; + visibleDisplayFrame.set(0, 0, displayWidth, displayHeight); + + visibleWinFrame.intersect(visibleDisplayFrame); + + if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top, + boundsInScreen.right, boundsInScreen.bottom)) { + info.setVisibleToUser(false); + } } } + private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, + MagnificationSpec spec) { + return (appScale != 1.0f || (spec != null && !spec.isNop())); + } /** * This class encapsulates a prefetching strategy for the accessibility APIs for diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 5e34a36..e996e67 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -28,6 +28,8 @@ import android.graphics.Matrix; * @hide */ public abstract class DisplayList { + private boolean mDirty; + /** * Flag used when calling * {@link HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)} @@ -96,6 +98,28 @@ public abstract class DisplayList { public abstract void clear(); /** + * Sets the dirty flag. When a display list is dirty, both + * {@link #invalidate()} and {@link #clear()} should be invoked whenever + * possible. + * + * @param dirty True to mark the display list dirty, false otherwise + * + * @see #isDirty() + */ + public void setDirty(boolean dirty) { + mDirty = dirty; + } + + /** + * Indicates whether the display list is dirty. + * + * @see #setDirty(boolean) + */ + public boolean isDirty() { + return mDirty; + } + + /** * Returns whether the display list is currently usable. If this returns false, * the display list should be re-recorded prior to replaying it. * diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index d2df45a..168ae81 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -24,10 +24,7 @@ import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; -import android.util.Pool; -import android.util.Poolable; -import android.util.PoolableManager; -import android.util.Pools; +import android.util.Pools.SynchronizedPool; /** * An implementation of a GL canvas that records drawing operations. @@ -35,26 +32,13 @@ import android.util.Pools; * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while * the DisplayList is still holding a native reference to the memory. */ -class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20RecordingCanvas> { +class GLES20RecordingCanvas extends GLES20Canvas { // The recording canvas pool should be large enough to handle a deeply nested // view hierarchy because display lists are generated recursively. private static final int POOL_LIMIT = 25; - private static final Pool<GLES20RecordingCanvas> sPool = Pools.synchronizedPool( - Pools.finitePool(new PoolableManager<GLES20RecordingCanvas>() { - public GLES20RecordingCanvas newInstance() { - return new GLES20RecordingCanvas(); - } - @Override - public void onAcquired(GLES20RecordingCanvas element) { - } - @Override - public void onReleased(GLES20RecordingCanvas element) { - } - }, POOL_LIMIT)); - - private GLES20RecordingCanvas mNextPoolable; - private boolean mIsPooled; + private static final SynchronizedPool<GLES20RecordingCanvas> sPool = + new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT); private GLES20DisplayList mDisplayList; @@ -64,6 +48,9 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) { GLES20RecordingCanvas canvas = sPool.acquire(); + if (canvas == null) { + canvas = new GLES20RecordingCanvas(); + } canvas.mDisplayList = displayList; return canvas; } @@ -300,24 +287,4 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor colorOffset, indices, indexOffset, indexCount, paint); recordShaderBitmap(paint); } - - @Override - public GLES20RecordingCanvas getNextPoolable() { - return mNextPoolable; - } - - @Override - public void setNextPoolable(GLES20RecordingCanvas element) { - mNextPoolable = element; - } - - @Override - public boolean isPooled() { - return mIsPooled; - } - - @Override - public void setPooled(boolean isPooled) { - mIsPooled = isPooled; - } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 5b7a5af..157c321 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -76,16 +76,6 @@ public abstract class HardwareRenderer { * "false", to disable partial invalidates */ static final String RENDER_DIRTY_REGIONS_PROPERTY = "debug.hwui.render_dirty_regions"; - - /** - * System property used to enable or disable vsync. - * The default value of this property is assumed to be false. - * - * Possible values: - * "true", to disable vsync - * "false", to enable vsync - */ - static final String DISABLE_VSYNC_PROPERTY = "debug.hwui.disable_vsync"; /** * System property used to enable or disable hardware rendering profiling. @@ -323,6 +313,20 @@ public abstract class HardwareRenderer { abstract long getFrameCount(); /** + * Loads system properties used by the renderer. This method is invoked + * whenever system properties are modified. Implementations can use this + * to trigger live updates of the renderer based on properties. + * + * @param surface The surface to update with the new properties. + * Can be null. + * + * @return True if a property has changed. + */ + abstract boolean loadSystemProperties(Surface surface); + + private static native boolean nLoadProperties(); + + /** * Sets the directory to use as a persistent storage for hardware rendering * resources. * @@ -373,15 +377,6 @@ public abstract class HardwareRenderer { private static native boolean nIsBackBufferPreserved(); /** - * Disables v-sync. For performance testing only. - */ - static void disableVsync() { - nDisableVsync(); - } - - private static native void nDisableVsync(); - - /** * Indicates that the specified hardware layer needs to be updated * as soon as possible. * @@ -652,15 +647,13 @@ public abstract class HardwareRenderer { boolean mDirtyRegionsEnabled; boolean mUpdateDirtyRegions; - final boolean mVsyncDisabled; - - final boolean mProfileEnabled; - final float[] mProfileData; - final ReentrantLock mProfileLock; + boolean mProfileEnabled; + float[] mProfileData; + ReentrantLock mProfileLock; int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; - final boolean mDebugDirtyRegions; - final boolean mShowOverdraw; + boolean mDebugDirtyRegions; + boolean mShowOverdraw; final int mGlVersion; final boolean mTranslucent; @@ -675,44 +668,67 @@ public abstract class HardwareRenderer { GlRenderer(int glVersion, boolean translucent) { mGlVersion = glVersion; mTranslucent = translucent; - - String property; - property = SystemProperties.get(DISABLE_VSYNC_PROPERTY, "false"); - mVsyncDisabled = "true".equalsIgnoreCase(property); - if (mVsyncDisabled) { - Log.d(LOG_TAG, "Disabling v-sync"); - } + loadSystemProperties(null); + } - property = SystemProperties.get(PROFILE_PROPERTY, "false"); - mProfileEnabled = "true".equalsIgnoreCase(property); - if (mProfileEnabled) { - Log.d(LOG_TAG, "Profiling hardware renderer"); - } + @Override + boolean loadSystemProperties(Surface surface) { + boolean changed = false; - if (mProfileEnabled) { - property = SystemProperties.get(PROFILE_MAXFRAMES_PROPERTY, - Integer.toString(PROFILE_MAX_FRAMES)); - int maxProfileFrames = Integer.valueOf(property); - mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; - for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { - mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; + boolean value = SystemProperties.getBoolean(PROFILE_PROPERTY, false); + if (value != mProfileEnabled) { + changed = true; + mProfileEnabled = value; + + if (mProfileEnabled) { + Log.d(LOG_TAG, "Profiling hardware renderer"); } - mProfileLock = new ReentrantLock(); - } else { - mProfileData = null; - mProfileLock = null; + if (mProfileEnabled) { + int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY, + PROFILE_MAX_FRAMES); + mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; + for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { + mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; + } + + mProfileLock = new ReentrantLock(); + } else { + mProfileData = null; + mProfileLock = null; + } } - property = SystemProperties.get(DEBUG_DIRTY_REGIONS_PROPERTY, "false"); - mDebugDirtyRegions = "true".equalsIgnoreCase(property); - if (mDebugDirtyRegions) { - Log.d(LOG_TAG, "Debugging dirty regions"); + value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false); + if (value != mDebugDirtyRegions) { + changed = true; + mDebugDirtyRegions = value; + + if (mDebugDirtyRegions) { + Log.d(LOG_TAG, "Debugging dirty regions"); + } } - mShowOverdraw = SystemProperties.getBoolean( + value = SystemProperties.getBoolean( HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false); + if (value != mShowOverdraw) { + changed = true; + mShowOverdraw = value; + + if (surface != null && isEnabled()) { + if (validate()) { + sEglConfig = loadEglConfig(); + invalidate(surface); + } + } + } + + if (nLoadProperties()) { + changed = true; + } + + return changed; } @Override @@ -842,19 +858,7 @@ public abstract class HardwareRenderer { checkEglErrorsForced(); - sEglConfig = chooseEglConfig(); - if (sEglConfig == null) { - // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without - if (sDirtyRegions) { - sDirtyRegions = false; - sEglConfig = chooseEglConfig(); - if (sEglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - } else { - throw new RuntimeException("eglConfig not initialized"); - } - } + sEglConfig = loadEglConfig(); } } @@ -868,6 +872,23 @@ public abstract class HardwareRenderer { } } + private EGLConfig loadEglConfig() { + EGLConfig eglConfig = chooseEglConfig(); + if (eglConfig == null) { + // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without + if (sDirtyRegions) { + sDirtyRegions = false; + eglConfig = chooseEglConfig(); + if (eglConfig == null) { + throw new RuntimeException("eglConfig not initialized"); + } + } else { + throw new RuntimeException("eglConfig not initialized"); + } + } + return eglConfig; + } + abstract ManagedEGLContext createManagedContext(EGLContext eglContext); private EGLConfig chooseEglConfig() { @@ -1484,14 +1505,6 @@ public abstract class HardwareRenderer { } @Override - void setup(int width, int height) { - super.setup(width, height); - if (mVsyncDisabled) { - disableVsync(); - } - } - - @Override void pushLayerUpdate(HardwareLayer layer) { mGlCanvas.pushLayerUpdate(layer); } diff --git a/core/java/android/view/IDisplayContentChangeListener.aidl b/core/java/android/view/IDisplayMagnificationController.aidl index ef7edea..efe2775 100644 --- a/core/java/android/view/IDisplayContentChangeListener.aidl +++ b/core/java/android/view/IDisplayMagnificationController.aidl @@ -1,7 +1,7 @@ /* ** Copyright 2012, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License") +** 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 ** @@ -16,18 +16,12 @@ package android.view; -import android.os.IBinder; -import android.view.WindowInfo; -import android.graphics.Rect; - /** - * Interface for observing content changes on a display. - * * {@hide} */ -oneway interface IDisplayContentChangeListener { - void onWindowTransition(int displayId, int transition, in WindowInfo info); - void onRectangleOnScreenRequested(int displayId, in Rect rectangle, boolean immediate); - void onWindowLayersChanged(int displayId); +oneway interface IDisplayMagnificationController { + void onMagnifedFrameChanged(int left, int top, int right, int bottom); + void onRectangleOnScreenRequested(int left, int top, int right, int bottom); void onRotationChanged(int rotation); + void onUserContextChanged(); } diff --git a/core/java/android/view/IDisplayMagnificationMediator.aidl b/core/java/android/view/IDisplayMagnificationMediator.aidl new file mode 100644 index 0000000..aa25dac --- /dev/null +++ b/core/java/android/view/IDisplayMagnificationMediator.aidl @@ -0,0 +1,31 @@ +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.view; + +import android.view.IDisplayMagnificationController; +import android.view.MagnificationSpec; + +/** + * {@hide} + */ +interface IDisplayMagnificationMediator { + void addController(int displayId, in IDisplayMagnificationController controller); + void removeController(in IDisplayMagnificationController controller); + void setMagnificationSpec(in IDisplayMagnificationController controller, + in MagnificationSpec spec); + MagnificationSpec getCompatibleMagnificationSpec(in IBinder windowToken); +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 2b6cbcf..17f04e9 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -27,17 +27,17 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IRemoteCallback; import android.view.IApplicationToken; -import android.view.IDisplayContentChangeListener; +import android.view.IDisplayMagnificationMediator; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindowSession; import android.view.KeyEvent; import android.view.InputEvent; +import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.InputChannel; import android.view.InputDevice; import android.view.IInputFilter; -import android.view.WindowInfo; /** * System private interface to the window manager. @@ -74,7 +74,7 @@ interface IWindowManager void setEventDispatching(boolean enabled); void addWindowToken(IBinder token, int type); void removeWindowToken(IBinder token); - void addAppToken(int addPos, int userId, IApplicationToken token, + void addAppToken(int addPos, IApplicationToken token, int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked); void setAppGroupId(IBinder token, int groupId); void setAppOrientation(IApplicationToken token, int requestedOrientation); @@ -221,39 +221,19 @@ interface IWindowManager IBinder getFocusedWindowToken(); /** - * Gets the compatibility scale of e window given its token. - */ - float getWindowCompatibilityScale(IBinder windowToken); - - /** * Sets an input filter for manipulating the input event stream. */ void setInputFilter(in IInputFilter filter); /** - * Sets the scale and offset for implementing accessibility magnification. - */ - void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY); - - /** - * Adds a listener for display content changes. - */ - void addDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener); - - /** - * Removes a listener for display content changes. - */ - void removeDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener); - - /** - * Gets the info for a window given its token. + * Gets the display magnification mediator. */ - WindowInfo getWindowInfo(IBinder token); + IDisplayMagnificationMediator getDisplayMagnificationMediator(); /** - * Gets the infos for all visible windows. + * Gets the frame of a window given its token. */ - void getVisibleWindowsForDisplay(int displayId, out List<WindowInfo> outInfos); + void getWindowFrame(IBinder token, out Rect outFrame); /** * Device is in safe mode. diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/MagnificationSpec.aidl index 23e927a..d5fbdef 100644 --- a/core/java/android/view/WindowInfo.aidl +++ b/core/java/android/view/MagnificationSpec.aidl @@ -17,4 +17,4 @@ package android.view; -parcelable WindowInfo; +parcelable MagnificationSpec; diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java new file mode 100644 index 0000000..7fb5615 --- /dev/null +++ b/core/java/android/view/MagnificationSpec.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Pools.SynchronizedPool; + +/** + * This class represents spec for performing screen magnification. + * + * @hide + */ +public class MagnificationSpec implements Parcelable { + private static final int MAX_POOL_SIZE = 20; + private static final SynchronizedPool<MagnificationSpec> sPool = + new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE); + + public float scale = 1.0f; + public float offsetX; + public float offsetY; + + private MagnificationSpec() { + /* do nothing - reducing visibility */ + } + + public void initialize(float scale, float offsetX, float offsetY) { + this.scale = scale; + this.offsetX = offsetX; + this.offsetY = offsetY; + } + + public boolean isNop() { + return scale == 1.0f && offsetX == 0 && offsetY == 0; + } + + public static MagnificationSpec obtain(MagnificationSpec other) { + MagnificationSpec info = obtain(); + info.scale = other.scale; + info.offsetX = other.offsetX; + info.offsetY = other.offsetY; + return info; + } + + public static MagnificationSpec obtain() { + MagnificationSpec spec = sPool.acquire(); + return (spec != null) ? spec : new MagnificationSpec(); + } + + public void recycle() { + clear(); + sPool.release(this); + } + + public void clear() { + scale = 1.0f; + offsetX = 0.0f; + offsetY = 0.0f; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeFloat(scale); + parcel.writeFloat(offsetX); + parcel.writeFloat(offsetY); + recycle(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("<scale:"); + builder.append(scale); + builder.append(",offsetX:"); + builder.append(offsetX); + builder.append(",offsetY:"); + builder.append(offsetY); + builder.append(">"); + return builder.toString(); + } + + private void initFromParcel(Parcel parcel) { + scale = parcel.readFloat(); + offsetX = parcel.readFloat(); + offsetY = parcel.readFloat(); + } + + public static final Creator<MagnificationSpec> CREATOR = new Creator<MagnificationSpec>() { + @Override + public MagnificationSpec[] newArray(int size) { + return new MagnificationSpec[size]; + } + + @Override + public MagnificationSpec createFromParcel(Parcel parcel) { + MagnificationSpec spec = MagnificationSpec.obtain(); + spec.initFromParcel(parcel); + return spec; + } + }; +} diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 0a81a71..f2c5eac 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -225,9 +225,6 @@ public class Surface implements Parcelable { // non compatibility mode. private Matrix mCompatibleMatrix; - private int mWidth; - private int mHeight; - private native void nativeCreate(SurfaceSession session, String name, int w, int h, int format, int flags) throws OutOfResourcesException; @@ -333,8 +330,6 @@ public class Surface implements Parcelable { checkHeadless(); mName = name; - mWidth = w; - mHeight = h; nativeCreate(session, name, w, h, format, flags); mCloseGuard.open("release"); @@ -543,7 +538,7 @@ public class Surface implements Parcelable { /** @hide */ public void setPosition(int x, int y) { - nativeSetPosition(x, y); + nativeSetPosition((float)x, (float)y); } /** @hide */ @@ -553,22 +548,10 @@ public class Surface implements Parcelable { /** @hide */ public void setSize(int w, int h) { - mWidth = w; - mHeight = h; nativeSetSize(w, h); } /** @hide */ - public int getWidth() { - return mWidth; - } - - /** @hide */ - public int getHeight() { - return mHeight; - } - - /** @hide */ public void hide() { nativeSetFlags(SURFACE_HIDDEN, SURFACE_HIDDEN); } @@ -825,6 +808,32 @@ public class Surface implements Parcelable { } /** + * Returns a human readable representation of a rotation. + * + * @param rotation The rotation. + * @return The rotation symbolic name. + */ + public static String rotationToString(int rotation) { + switch (rotation) { + case Surface.ROTATION_0: { + return "ROTATION_0"; + } + case Surface.ROTATION_90: { + return "ROATATION_90"; + } + case Surface.ROTATION_180: { + return "ROATATION_180"; + } + case Surface.ROTATION_270: { + return "ROATATION_270"; + } + default: { + throw new IllegalArgumentException("Invalid rotation: " + rotation); + } + } + } + + /** * A Canvas class that can handle the compatibility mode. * This does two things differently. * <ul> diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index 82b3963..eb81f72 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -16,10 +16,7 @@ package android.view; -import android.util.Poolable; -import android.util.Pool; -import android.util.Pools; -import android.util.PoolableManager; +import android.util.Pools.SynchronizedPool; /** * Helper for tracking the velocity of touch events, for implementing @@ -31,30 +28,15 @@ import android.util.PoolableManager; * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)} * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id. */ -public final class VelocityTracker implements Poolable<VelocityTracker> { - private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( - Pools.finitePool(new PoolableManager<VelocityTracker>() { - public VelocityTracker newInstance() { - return new VelocityTracker(null); - } - - public void onAcquired(VelocityTracker element) { - // Intentionally empty - } - - public void onReleased(VelocityTracker element) { - element.clear(); - } - }, 2)); +public final class VelocityTracker { + private static final SynchronizedPool<VelocityTracker> sPool = + new SynchronizedPool<VelocityTracker>(2); private static final int ACTIVE_POINTER_ID = -1; private int mPtr; private final String mStrategy; - private VelocityTracker mNext; - private boolean mIsPooled; - private static native int nativeInitialize(String strategy); private static native void nativeDispose(int ptr); private static native void nativeClear(int ptr); @@ -73,7 +55,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return Returns a new VelocityTracker. */ static public VelocityTracker obtain() { - return sPool.acquire(); + VelocityTracker instance = sPool.acquire(); + return (instance != null) ? instance : new VelocityTracker(null); } /** @@ -98,38 +81,11 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { */ public void recycle() { if (mStrategy == null) { + clear(); sPool.release(this); } } - /** - * @hide - */ - public void setNextPoolable(VelocityTracker element) { - mNext = element; - } - - /** - * @hide - */ - public VelocityTracker getNextPoolable() { - return mNext; - } - - /** - * @hide - */ - public boolean isPooled() { - return mIsPooled; - } - - /** - * @hide - */ - public void setPooled(boolean isPooled) { - mIsPooled = isPooled; - } - private VelocityTracker(String strategy) { mPtr = nativeInitialize(strategy); mStrategy = strategy; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f05371a..080e7c0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -40,6 +40,7 @@ import android.graphics.Shader; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManagerGlobal; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -52,10 +53,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; -import android.util.Pool; -import android.util.Poolable; -import android.util.PoolableManager; -import android.util.Pools; +import android.util.Pools.SynchronizedPool; import android.util.Property; import android.util.SparseArray; import android.util.TypedValue; @@ -691,6 +689,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int NO_ID = -1; + private static boolean sUseBrokenMakeMeasureSpec = false; + /** * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when * calling setFlags. @@ -3237,6 +3237,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; + + if (!sUseBrokenMakeMeasureSpec && context.getApplicationInfo().targetSdkVersion <= + Build.VERSION_CODES.JELLY_BEAN_MR1 ) { + // Older apps may need this compatibility hack for measurement. + sUseBrokenMakeMeasureSpec = true; + } } /** @@ -10750,7 +10756,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { - final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire(); + final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain(); info.target = this; info.left = left; info.top = top; @@ -10799,7 +10805,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { - final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire(); + final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain(); info.target = this; info.left = left; info.top = top; @@ -11565,8 +11571,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, imm.focusIn(this); } - if (mAttachInfo != null && mDisplayList != null) { - mAttachInfo.mViewRootImpl.dequeueDisplayList(mDisplayList); + if (mDisplayList != null) { + mDisplayList.setDirty(false); } } @@ -11846,6 +11852,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo != null) { if (mDisplayList != null) { + mDisplayList.setDirty(true); mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList); } mAttachInfo.mViewRootImpl.cancelInvalidate(this); @@ -13978,6 +13985,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Return true if o is a ViewGroup that is laying out using optical bounds. + * @hide + */ + public static boolean isLayoutModeOptical(Object o) { + return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical(); + } + + private boolean setOpticalFrame(int left, int top, int right, int bottom) { + Insets parentInsets = mParent instanceof View ? + ((View) mParent).getOpticalInsets() : Insets.NONE; + Insets childInsets = getOpticalInsets(); + return setFrame( + left + parentInsets.left - childInsets.left, + top + parentInsets.top - childInsets.top, + right + parentInsets.left + childInsets.right, + bottom + parentInsets.top + childInsets.bottom); + } + + /** * Assign a size and position to a view and all of its * descendants * @@ -14003,7 +14029,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int oldT = mTop; int oldB = mBottom; int oldR = mRight; - boolean changed = setFrame(l, t, r, b); + boolean changed = isLayoutModeOptical(mParent) ? + setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; @@ -14445,6 +14472,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mBackground instanceof ColorDrawable) { ((ColorDrawable) mBackground.mutate()).setColor(color); computeOpaqueFlags(); + mBackgroundResource = 0; } else { setBackground(new ColorDrawable(color)); } @@ -14819,6 +14847,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return (mUserPaddingStart != UNDEFINED_PADDING || mUserPaddingEnd != UNDEFINED_PADDING); } + Insets computeOpticalInsets() { + return (mBackground == null) ? Insets.NONE : mBackground.getOpticalInsets(); + } + /** * @hide */ @@ -14842,19 +14874,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public Insets getOpticalInsets() { if (mLayoutInsets == null) { - mLayoutInsets = (mBackground == null) ? Insets.NONE : mBackground.getLayoutInsets(); + mLayoutInsets = computeOpticalInsets(); } return mLayoutInsets; } /** - * @hide - */ - public void setLayoutInsets(Insets layoutInsets) { - mLayoutInsets = layoutInsets; - } - - /** * Changes the selection state of this view. A view can be selected or not. * Note that selection is not the same as focus. Views are typically * selected in the context of an AdapterView like ListView or GridView; @@ -15461,11 +15486,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns whether the view hierarchy is currently undergoing a layout pass. This + * information is useful to avoid situations such as calling {@link #requestLayout()} during + * a layout pass. + * + * @return whether the view hierarchy is currently undergoing a layout pass + */ + public boolean isInLayout() { + ViewRootImpl viewRoot = getViewRootImpl(); + return (viewRoot != null && viewRoot.isInLayout()); + } + + /** * Call this when something has changed which has invalidated the * layout of this view. This will schedule a layout pass of the view - * tree. + * tree. This should not be called while the view hierarchy is currently in a layout + * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the + * end of the current layout pass (and then layout will run again) or after the current + * frame is drawn and the next layout occurs. + * + * <p>Subclasses which override this method should call the superclass method to + * handle possible request-during-layout errors correctly.</p> */ public void requestLayout() { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null && viewRoot.isInLayout()) { + viewRoot.requestLayoutDuringLayout(this); + return; + } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; @@ -15505,6 +15553,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { + boolean optical = isLayoutModeOptical(this); + if (optical != isLayoutModeOptical(mParent)) { + Insets insets = getOpticalInsets(); + int oWidth = insets.left + insets.right; + int oHeight = insets.top + insets.bottom; + widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); + heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); + } if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { @@ -15596,6 +15652,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link #MEASURED_STATE_TOO_SMALL}. */ protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { + boolean optical = isLayoutModeOptical(this); + if (optical != isLayoutModeOptical(mParent)) { + Insets insets = getOpticalInsets(); + int opticalWidth = insets.left + insets.right; + int opticalHeight = insets.top + insets.bottom; + + measuredWidth += optical ? opticalWidth : -opticalWidth; + measuredHeight += optical ? opticalHeight : -opticalHeight; + } mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; @@ -17267,12 +17332,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> * </ul> * + * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's + * implementation was such that the order of arguments did not matter + * and overflow in either value could impact the resulting MeasureSpec. + * {@link android.widget.RelativeLayout} was affected by this bug. + * Apps targeting API levels greater than 17 will get the fixed, more strict + * behavior.</p> + * * @param size the size of the measure specification * @param mode the mode of the measure specification * @return the measure specification based on size and mode */ public static int makeMeasureSpec(int size, int mode) { - return size + mode; + if (sUseBrokenMakeMeasureSpec) { + return size + mode; + } else { + return (size & ~MODE_MASK) | (mode & MODE_MASK); + } } /** @@ -17297,6 +17373,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return (measureSpec & ~MODE_MASK); } + static int adjust(int measureSpec, int delta) { + return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec)); + } + /** * Returns a String representation of the specified measure * specification. @@ -17634,25 +17714,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * POOL_LIMIT objects that get reused. This reduces memory allocations * whenever possible. */ - static class InvalidateInfo implements Poolable<InvalidateInfo> { + static class InvalidateInfo { private static final int POOL_LIMIT = 10; - private static final Pool<InvalidateInfo> sPool = Pools.synchronizedPool( - Pools.finitePool(new PoolableManager<InvalidateInfo>() { - public InvalidateInfo newInstance() { - return new InvalidateInfo(); - } - public void onAcquired(InvalidateInfo element) { - } - - public void onReleased(InvalidateInfo element) { - element.target = null; - } - }, POOL_LIMIT) - ); - - private InvalidateInfo mNext; - private boolean mIsPooled; + private static final SynchronizedPool<InvalidateInfo> sPool = + new SynchronizedPool<InvalidateInfo>(POOL_LIMIT); View target; @@ -17661,29 +17727,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int right; int bottom; - public void setNextPoolable(InvalidateInfo element) { - mNext = element; + public static InvalidateInfo obtain() { + InvalidateInfo instance = sPool.acquire(); + return (instance != null) ? instance : new InvalidateInfo(); } - public InvalidateInfo getNextPoolable() { - return mNext; - } - - static InvalidateInfo acquire() { - return sPool.acquire(); - } - - void release() { + public void recycle() { + target = null; sPool.release(this); } - - public boolean isPooled() { - return mIsPooled; - } - - public void setPooled(boolean isPooled) { - mIsPooled = isPooled; - } } final IWindowSession mSession; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index dbbcde6..c274f27 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -35,6 +35,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; +import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -83,6 +84,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private static final String TAG = "ViewGroup"; private static final boolean DBG = false; + /** @hide */ + public static boolean DEBUG_DRAW = false; /** * Views which have been hidden or removed which need to be animated on @@ -180,10 +183,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager }) protected int mGroupFlags; - /* - * The layout mode: either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS} + /** + * Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}. */ - private int mLayoutMode = CLIP_BOUNDS; + private int mLayoutMode = DEFAULT_LAYOUT_MODE; /** * NOTE: If you change the flags below make sure to reflect the changes @@ -356,20 +359,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * This constant is a {@link #setLayoutMode(int) layoutMode}. * Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top}, * {@link #getRight() right} and {@link #getBottom() bottom}. - * - * @hide */ - public static final int CLIP_BOUNDS = 0; + public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; /** * This constant is a {@link #setLayoutMode(int) layoutMode}. * Optical bounds describe where a widget appears to be. They sit inside the clip * bounds which need to cover a larger area to allow other effects, * such as shadows and glows, to be drawn. - * - * @hide */ - public static final int OPTICAL_BOUNDS = 1; + public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; + + /** @hide */ + public static int DEFAULT_LAYOUT_MODE = LAYOUT_MODE_CLIP_BOUNDS; /** * We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL @@ -434,7 +436,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } private boolean debugDraw() { - return mAttachInfo != null && mAttachInfo.mDebugLayout; + return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout; } private void initViewGroup() { @@ -504,6 +506,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager setLayoutTransition(new LayoutTransition()); } break; + case R.styleable.ViewGroup_layoutMode: + setLayoutMode(a.getInt(attr, DEFAULT_LAYOUT_MODE)); + break; } } @@ -2420,7 +2425,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < count; i++) { final View child = children[i]; child.dispatchAttachedToWindow(info, - visibility | (child.mViewFlags&VISIBILITY_MASK)); + visibility | (child.mViewFlags & VISIBILITY_MASK)); } } @@ -2682,20 +2687,89 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return b; } - private static void drawRect(Canvas canvas, int x1, int y1, int x2, int y2, int color) { - Paint paint = getDebugPaint(); - paint.setColor(color); + /** Return true if this ViewGroup is laying out using optical bounds. */ + boolean isLayoutModeOptical() { + return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS; + } + + Insets computeOpticalInsets() { + if (isLayoutModeOptical()) { + int left = 0; + int top = 0; + int right = 0; + int bottom = 0; + for (int i = 0; i < mChildrenCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + Insets insets = child.getOpticalInsets(); + left = Math.max(left, insets.left); + top = Math.max(top, insets.top); + right = Math.max(right, insets.right); + bottom = Math.max(bottom, insets.bottom); + } + } + return Insets.of(left, top, right, bottom); + } else { + return Insets.NONE; + } + } + + private static void fillRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) { + if (x1 != x2 && y1 != y2) { + if (x1 > x2) { + int tmp = x1; x1 = x2; x2 = tmp; + } + if (y1 > y2) { + int tmp = y1; y1 = y2; y2 = tmp; + } + canvas.drawRect(x1, y1, x2, y2, paint); + } + } + + private static int sign(int x) { + return (x >= 0) ? 1 : -1; + } + + private static void drawCorner(Canvas c, Paint paint, int x1, int y1, int dx, int dy, int lw) { + fillRect(c, paint, x1, y1, x1 + dx, y1 + lw * sign(dy)); + fillRect(c, paint, x1, y1, x1 + lw * sign(dx), y1 + dy); + } - canvas.drawLines(getDebugLines(x1, y1, x2, y2), paint); + private int dipsToPixels(int dips) { + float scale = getContext().getResources().getDisplayMetrics().density; + return (int) (dips * scale + 0.5f); + } + + private void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint, + int lineLength, int lineWidth) { + drawCorner(canvas, paint, x1, y1, lineLength, lineLength, lineWidth); + drawCorner(canvas, paint, x1, y2, lineLength, -lineLength, lineWidth); + drawCorner(canvas, paint, x2, y1, -lineLength, lineLength, lineWidth); + drawCorner(canvas, paint, x2, y2, -lineLength, -lineLength, lineWidth); + } + + private static void fillDifference(Canvas canvas, + int x2, int y2, int x3, int y3, + int dx1, int dy1, int dx2, int dy2, Paint paint) { + int x1 = x2 - dx1; + int y1 = y2 - dy1; + + int x4 = x3 + dx2; + int y4 = y3 + dy2; + + fillRect(canvas, paint, x1, y1, x4, y2); + fillRect(canvas, paint, x1, y2, x2, y3); + fillRect(canvas, paint, x3, y2, x4, y3); + fillRect(canvas, paint, x1, y3, x4, y4); } /** * @hide */ - protected void onDebugDrawMargins(Canvas canvas) { + protected void onDebugDrawMargins(Canvas canvas, Paint paint) { for (int i = 0; i < getChildCount(); i++) { View c = getChildAt(i); - c.getLayoutParams().onDebugDraw(c, canvas); + c.getLayoutParams().onDebugDraw(c, canvas, paint); } } @@ -2703,26 +2777,45 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ protected void onDebugDraw(Canvas canvas) { + Paint paint = getDebugPaint(); + // Draw optical bounds - if (getLayoutMode() == OPTICAL_BOUNDS) { + { + paint.setColor(Color.RED); + paint.setStyle(Paint.Style.STROKE); + for (int i = 0; i < getChildCount(); i++) { View c = getChildAt(i); Insets insets = c.getOpticalInsets(); - drawRect(canvas, - c.getLeft() + insets.left, - c.getTop() + insets.top, - c.getRight() - insets.right, - c.getBottom() - insets.bottom, Color.RED); + + drawRect(canvas, paint, + c.getLeft() + insets.left, + c.getTop() + insets.top, + c.getRight() - insets.right - 1, + c.getBottom() - insets.bottom - 1); } } // Draw margins - onDebugDrawMargins(canvas); + { + paint.setColor(Color.argb(63, 255, 0, 255)); + paint.setStyle(Paint.Style.FILL); - // Draw bounds - for (int i = 0; i < getChildCount(); i++) { - View c = getChildAt(i); - drawRect(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(), Color.BLUE); + onDebugDrawMargins(canvas, paint); + } + + // Draw clip bounds + { + paint.setColor(Color.rgb(63, 127, 255)); + paint.setStyle(Paint.Style.FILL); + + int lineLength = dipsToPixels(8); + int lineWidth = dipsToPixels(1); + for (int i = 0; i < getChildCount(); i++) { + View c = getChildAt(i); + drawRectCorners(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(), + paint, lineLength, lineWidth); + } } } @@ -3620,8 +3713,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager childHasTransientStateChanged(view, false); } - onViewRemoved(view); - needGlobalAttributesUpdate(false); removeFromArray(index); @@ -3634,6 +3725,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (view.isAccessibilityFocused()) { view.clearAccessibilityFocus(); } + + onViewRemoved(view); } /** @@ -4610,13 +4703,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Returns the basis of alignment during layout operations on this view group: - * either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}. + * either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}. * * @return the layout mode to use during layout operations * * @see #setLayoutMode(int) - * - * @hide */ public int getLayoutMode() { return mLayoutMode; @@ -4624,15 +4715,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Sets the basis of alignment during the layout of this view group. - * Valid values are either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}. + * Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or + * {@link #LAYOUT_MODE_OPTICAL_BOUNDS}. * <p> - * The default is {@link #CLIP_BOUNDS}. + * The default is {@link #LAYOUT_MODE_CLIP_BOUNDS}. * * @param layoutMode the layout mode to use during layout operations * * @see #getLayoutMode() - * - * @hide */ public void setLayoutMode(int layoutMode) { if (mLayoutMode != layoutMode) { @@ -5650,7 +5740,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * * @hide */ - public void onDebugDraw(View view, Canvas canvas) { + public void onDebugDraw(View view, Canvas canvas, Paint paint) { } /** @@ -5998,12 +6088,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override - public void onDebugDraw(View view, Canvas canvas) { - drawRect(canvas, - view.getLeft() - leftMargin, - view.getTop() - topMargin, - view.getRight() + rightMargin, - view.getBottom() + bottomMargin, Color.MAGENTA); + public void onDebugDraw(View view, Canvas canvas, Paint paint) { + Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE; + + fillDifference(canvas, + view.getLeft() + oi.left, + view.getTop() + oi.top, + view.getRight() - oi.right, + view.getBottom() - oi.bottom, + leftMargin, + topMargin, + rightMargin, + bottomMargin, + paint); } } @@ -6119,50 +6216,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private static final int MAX_POOL_SIZE = 32; - private static final Object sPoolLock = new Object(); - - private static ChildListForAccessibility sPool; - - private static int sPoolSize; - - private boolean mIsPooled; - - private ChildListForAccessibility mNext; + private static final SynchronizedPool<ChildListForAccessibility> sPool = + new SynchronizedPool<ChildListForAccessibility>(MAX_POOL_SIZE); private final ArrayList<View> mChildren = new ArrayList<View>(); private final ArrayList<ViewLocationHolder> mHolders = new ArrayList<ViewLocationHolder>(); public static ChildListForAccessibility obtain(ViewGroup parent, boolean sort) { - ChildListForAccessibility list = null; - synchronized (sPoolLock) { - if (sPool != null) { - list = sPool; - sPool = list.mNext; - list.mNext = null; - list.mIsPooled = false; - sPoolSize--; - } else { - list = new ChildListForAccessibility(); - } - list.init(parent, sort); - return list; + ChildListForAccessibility list = sPool.acquire(); + if (list == null) { + list = new ChildListForAccessibility(); } + list.init(parent, sort); + return list; } public void recycle() { - if (mIsPooled) { - throw new IllegalStateException("Instance already recycled."); - } clear(); - synchronized (sPoolLock) { - if (sPoolSize < MAX_POOL_SIZE) { - mNext = sPool; - mIsPooled = true; - sPool = this; - sPoolSize++; - } - } + sPool.release(this); } public int getChildCount() { @@ -6216,15 +6288,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private static final int MAX_POOL_SIZE = 32; - private static final Object sPoolLock = new Object(); - - private static ViewLocationHolder sPool; - - private static int sPoolSize; - - private boolean mIsPooled; - - private ViewLocationHolder mNext; + private static final SynchronizedPool<ViewLocationHolder> sPool = + new SynchronizedPool<ViewLocationHolder>(MAX_POOL_SIZE); private final Rect mLocation = new Rect(); @@ -6233,35 +6298,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private int mLayoutDirection; public static ViewLocationHolder obtain(ViewGroup root, View view) { - ViewLocationHolder holder = null; - synchronized (sPoolLock) { - if (sPool != null) { - holder = sPool; - sPool = holder.mNext; - holder.mNext = null; - holder.mIsPooled = false; - sPoolSize--; - } else { - holder = new ViewLocationHolder(); - } - holder.init(root, view); - return holder; + ViewLocationHolder holder = sPool.acquire(); + if (holder == null) { + holder = new ViewLocationHolder(); } + holder.init(root, view); + return holder; } public void recycle() { - if (mIsPooled) { - throw new IllegalStateException("Instance already recycled."); - } clear(); - synchronized (sPoolLock) { - if (sPoolSize < MAX_POOL_SIZE) { - mNext = sPool; - mIsPooled = true; - sPool = this; - sPoolSize++; - } - } + sPool.release(this); } @Override @@ -6337,14 +6384,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return sDebugPaint; } - private static float[] getDebugLines(int x1, int y1, int x2, int y2) { + private void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) { if (sDebugLines== null) { sDebugLines = new float[16]; } - x2--; - y2--; - sDebugLines[0] = x1; sDebugLines[1] = y1; sDebugLines[2] = x2; @@ -6353,18 +6397,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager sDebugLines[4] = x2; sDebugLines[5] = y1; sDebugLines[6] = x2; - sDebugLines[7] = y2 + 1; + sDebugLines[7] = y2; - sDebugLines[8] = x2 + 1; + sDebugLines[8] = x2; sDebugLines[9] = y2; sDebugLines[10] = x1; sDebugLines[11] = y2; - sDebugLines[12] = x1; - sDebugLines[13] = y2; + sDebugLines[12] = x1; + sDebugLines[13] = y2; sDebugLines[14] = x1; sDebugLines[15] = y1; - return sDebugLines; + canvas.drawLines(sDebugLines, paint); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 27d770b..85fab28 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -114,7 +114,7 @@ public final class ViewRootImpl implements ViewParent, * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. */ - private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering"; + private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering"; private static final boolean MEASURE_LATENCY = false; private static LatencyTimer lt; @@ -289,15 +289,15 @@ public final class ViewRootImpl implements ViewParent, final PointF mLastTouchPoint = new PointF(); private boolean mProfileRendering; - private Thread mRenderProfiler; - private volatile boolean mRenderProfilingEnabled; + private Choreographer.FrameCallback mRenderProfiler; + private boolean mRenderProfilingEnabled; // Variables to track frames per second, enabled via DEBUG_FPS flag private long mFpsStartTime = -1; private long mFpsPrevTime = -1; private int mFpsNumFrames; - private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>(24); + private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>(); /** * see {@link #playSoundEffect(int)} @@ -317,6 +317,10 @@ public final class ViewRootImpl implements ViewParent, private final int mDensity; private final int mNoncompatDensity; + private boolean mInLayout = false; + ArrayList<View> mLayoutRequesters = new ArrayList<View>(); + boolean mHandlingLayoutInLayoutRequest = false; + private int mViewLayoutDirectionInitial; /** @@ -378,8 +382,6 @@ public final class ViewRootImpl implements ViewParent, mDensity = context.getResources().getDisplayMetrics().densityDpi; mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context); - mProfileRendering = Boolean.parseBoolean( - SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false")); mChoreographer = Choreographer.getInstance(); PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); @@ -822,9 +824,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void requestLayout() { - checkThread(); - mLayoutRequested = true; - scheduleTraversals(); + if (!mHandlingLayoutInLayoutRequest) { + checkThread(); + mLayoutRequested = true; + scheduleTraversals(); + } } @Override @@ -1710,7 +1714,7 @@ public final class ViewRootImpl implements ViewParent, boolean triggerGlobalLayoutListener = didLayout || attachInfo.mRecomputeGlobalAttributes; if (didLayout) { - performLayout(); + performLayout(lp, desiredWindowWidth, desiredWindowHeight); // By this point all views have been sized and positionned // We can compute the transparent area @@ -1879,9 +1883,50 @@ public final class ViewRootImpl implements ViewParent, } } - private void performLayout() { + /** + * Called by {@link android.view.View#isInLayout()} to determine whether the view hierarchy + * is currently undergoing a layout pass. + * + * @return whether the view hierarchy is currently undergoing a layout pass + */ + boolean isInLayout() { + return mInLayout; + } + + /** + * Called by {@link android.view.View#requestLayout()} if the view hiearchy is currently + * undergoing a layout pass. requestLayout() should not be called during layout, but if it + * is called anyway, we handle the situation here rather than leave the hierarchy in an + * indeterminate state. The solution is to queue up all requests during layout, apply those + * requests as soon as layout is complete, and then run layout once more immediately. If + * more requestLayout() calls are received during that second layout pass, we post those + * requests to the next frame, to avoid possible infinite loops. + * + * @param view the view that requested the layout. + */ + void requestLayoutDuringLayout(final View view) { + if (!mHandlingLayoutInLayoutRequest) { + if (!mLayoutRequesters.contains(view)) { + Log.w("View", "requestLayout() called by " + view + " during layout pass"); + mLayoutRequesters.add(view); + } + } else { + Log.w("View", "requestLayout() called by " + view + " during second layout pass: " + + "posting to next frame"); + view.post(new Runnable() { + @Override + public void run() { + view.requestLayout(); + } + }); + } + } + + private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, + int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; + mInLayout = true; final View host = mView; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { @@ -1892,9 +1937,26 @@ public final class ViewRootImpl implements ViewParent, Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); + mInLayout = false; + int numViewsRequestingLayout = mLayoutRequesters.size(); + if (numViewsRequestingLayout > 0) { + // requestLayout() was called during layout: unusual, but try to handle correctly + mHandlingLayoutInLayoutRequest = true; + for (int i = 0; i < numViewsRequestingLayout; ++i) { + mLayoutRequesters.get(i).requestLayout(); + } + measureHierarchy(host, lp, mView.getContext().getResources(), + desiredWindowWidth, desiredWindowHeight); + // Now run layout one more time + mInLayout = true; + host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); + mHandlingLayoutInLayoutRequest = false; + mLayoutRequesters.clear(); + } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + mInLayout = false; } public void requestTransparentRegion(View child) { @@ -1978,30 +2040,19 @@ public final class ViewRootImpl implements ViewParent, if (mProfileRendering) { mRenderProfilingEnabled = enabled; if (mRenderProfiler == null) { - mRenderProfiler = new Thread(new Runnable() { + mRenderProfiler = new Choreographer.FrameCallback() { @Override - public void run() { - Log.d(TAG, "Starting profiling thread"); - while (mRenderProfilingEnabled) { - mAttachInfo.mHandler.post(new Runnable() { - @Override - public void run() { - mDirty.set(0, 0, mWidth, mHeight); - scheduleTraversals(); - } - }); - try { - // TODO: This should use vsync when we get an API - Thread.sleep(15); - } catch (InterruptedException e) { - Log.d(TAG, "Exiting profiling thread"); - } + public void doFrame(long frameTimeNanos) { + mDirty.set(0, 0, mWidth, mHeight); + scheduleTraversals(); + if (mRenderProfilingEnabled) { + mChoreographer.postFrameCallback(mRenderProfiler); } } - }, "Rendering Profiler"); - mRenderProfiler.start(); + }; + mChoreographer.postFrameCallback(mRenderProfiler); } else { - mRenderProfiler.interrupt(); + mChoreographer.removeFrameCallback(mRenderProfiler); mRenderProfiler = null; } } @@ -2077,7 +2128,7 @@ public final class ViewRootImpl implements ViewParent, private void draw(boolean fullRedrawNeeded) { Surface surface = mSurface; - if (surface == null || !surface.isValid()) { + if (!surface.isValid()) { return; } @@ -2158,6 +2209,8 @@ public final class ViewRootImpl implements ViewParent, appScale + ", width=" + mWidth + ", height=" + mHeight); } + invalidateDisplayLists(); + attachInfo.mTreeObserver.dispatchOnDraw(); if (!dirty.isEmpty() || mIsAnimating) { @@ -2368,8 +2421,11 @@ public final class ViewRootImpl implements ViewParent, for (int i = 0; i < count; i++) { final DisplayList displayList = displayLists.get(i); - displayList.invalidate(); - displayList.clear(); + if (displayList.isDirty()) { + displayList.invalidate(); + displayList.clear(); + displayList.setDirty(false); + } } displayLists.clear(); @@ -2743,11 +2799,10 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_UPDATE_CONFIGURATION = 18; private final static int MSG_PROCESS_INPUT_EVENTS = 19; private final static int MSG_DISPATCH_SCREEN_STATE = 20; - private final static int MSG_INVALIDATE_DISPLAY_LIST = 21; - private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 22; - private final static int MSG_DISPATCH_DONE_ANIMATING = 23; - private final static int MSG_INVALIDATE_WORLD = 24; - private final static int MSG_WINDOW_MOVED = 25; + private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21; + private final static int MSG_DISPATCH_DONE_ANIMATING = 22; + private final static int MSG_INVALIDATE_WORLD = 23; + private final static int MSG_WINDOW_MOVED = 24; final class ViewRootHandler extends Handler { @Override @@ -2793,8 +2848,6 @@ public final class ViewRootImpl implements ViewParent, return "MSG_PROCESS_INPUT_EVENTS"; case MSG_DISPATCH_SCREEN_STATE: return "MSG_DISPATCH_SCREEN_STATE"; - case MSG_INVALIDATE_DISPLAY_LIST: - return "MSG_INVALIDATE_DISPLAY_LIST"; case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST"; case MSG_DISPATCH_DONE_ANIMATING: @@ -2814,7 +2867,7 @@ public final class ViewRootImpl implements ViewParent, case MSG_INVALIDATE_RECT: final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj; info.target.invalidate(info.left, info.top, info.right, info.bottom); - info.release(); + info.recycle(); break; case MSG_IME_FINISHED_EVENT: handleImeFinishedEvent(msg.arg1, msg.arg2 != 0); @@ -3001,7 +3054,7 @@ public final class ViewRootImpl implements ViewParent, handleDragEvent(event); } break; case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: { - handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo)msg.obj); + handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj); } break; case MSG_UPDATE_CONFIGURATION: { Configuration config = (Configuration)msg.obj; @@ -3015,9 +3068,6 @@ public final class ViewRootImpl implements ViewParent, handleScreenStateChange(msg.arg1 == 1); } } break; - case MSG_INVALIDATE_DISPLAY_LIST: { - invalidateDisplayLists(); - } break; case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { setAccessibilityFocus(null, null); } break; @@ -4102,6 +4152,7 @@ public final class ViewRootImpl implements ViewParent, } if (mAdded && !mFirst) { + invalidateDisplayLists(); destroyHardwareRenderer(); if (mView != null) { @@ -4134,14 +4185,30 @@ public final class ViewRootImpl implements ViewParent, } public void loadSystemProperties() { - boolean layout = SystemProperties.getBoolean( - View.DEBUG_LAYOUT_PROPERTY, false); - if (layout != mAttachInfo.mDebugLayout) { - mAttachInfo.mDebugLayout = layout; - if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) { - mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200); + mHandler.post(new Runnable() { + @Override + public void run() { + // Profiling + mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false); + profileRendering(mAttachInfo.mHasWindowFocus); + + // Hardware rendering + if (mAttachInfo.mHardwareRenderer != null) { + if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) { + invalidate(); + } + } + + // Layout debugging + boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false); + if (layout != mAttachInfo.mDebugLayout) { + mAttachInfo.mDebugLayout = layout; + if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) { + mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200); + } + } } - } + }); } private void destroyHardwareRenderer() { @@ -4429,7 +4496,7 @@ public final class ViewRootImpl implements ViewParent, AttachInfo.InvalidateInfo info = mViewRects.get(i); if (info.target == view) { mViewRects.remove(i); - info.release(); + info.recycle(); } } @@ -4470,7 +4537,7 @@ public final class ViewRootImpl implements ViewParent, for (int i = 0; i < viewRectCount; i++) { final View.AttachInfo.InvalidateInfo info = mTempViewRects[i]; info.target.invalidate(info.left, info.top, info.right, info.bottom); - info.release(); + info.recycle(); } } @@ -4505,19 +4572,6 @@ public final class ViewRootImpl implements ViewParent, public void enqueueDisplayList(DisplayList displayList) { mDisplayLists.add(displayList); - - mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST); - Message msg = mHandler.obtainMessage(MSG_INVALIDATE_DISPLAY_LIST); - mHandler.sendMessage(msg); - } - - public void dequeueDisplayList(DisplayList displayList) { - if (mDisplayLists.remove(displayList)) { - displayList.invalidate(); - if (mDisplayLists.size() == 0) { - mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST); - } - } } public void cancelInvalidate(View view) { @@ -5354,12 +5408,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid) { + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId, - interactionId, callback, flags, interrogatingPid, interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid, + spec); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5393,12 +5448,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid) { + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId, - interactionId, callback, flags, interrogatingPid, interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid, + spec); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5412,12 +5468,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid) { + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text, - interactionId, callback, flags, interrogatingPid, interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid, + spec); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5431,12 +5488,12 @@ public final class ViewRootImpl implements ViewParent, @Override public void findFocus(long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid) { + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .findFocusClientThread(accessibilityNodeId, focusType, interactionId, callback, - flags, interrogatingPid, interrogatingTid); + flags, interrogatingPid, interrogatingTid, spec); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5450,12 +5507,12 @@ public final class ViewRootImpl implements ViewParent, @Override public void focusSearch(long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid) { + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .focusSearchClientThread(accessibilityNodeId, direction, interactionId, - callback, flags, interrogatingPid, interrogatingTid); + callback, flags, interrogatingPid, interrogatingTid, spec); } else { // We cannot make the call and notify the caller so it does not wait. try { diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index d7c7f46..6251c45 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -218,12 +218,14 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private static class WarningDialogReceiver extends BroadcastReceiver implements DialogInterface.OnDismissListener { - private Context mContext; - private Dialog mDialog; + private final Context mContext; + private final Dialog mDialog; + private final VolumePanel mVolumePanel; - WarningDialogReceiver(Context context, Dialog dialog) { + WarningDialogReceiver(Context context, Dialog dialog, VolumePanel volumePanel) { mContext = context; mDialog = dialog; + mVolumePanel = volumePanel; IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(this, filter); } @@ -231,16 +233,20 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie @Override public void onReceive(Context context, Intent intent) { mDialog.cancel(); - synchronized (sConfirmSafeVolumeLock) { - sConfirmSafeVolumeDialog = null; - } + cleanUp(); } public void onDismiss(DialogInterface unused) { mContext.unregisterReceiver(this); + cleanUp(); + } + + private void cleanUp() { synchronized (sConfirmSafeVolumeLock) { sConfirmSafeVolumeDialog = null; } + mVolumePanel.forceTimeout(); + mVolumePanel.updateStates(); } } @@ -276,7 +282,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie mDialog = new Dialog(context, R.style.Theme_Panel_Volume) { public boolean onTouchEvent(MotionEvent event) { - if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE) { + if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && + sConfirmSafeVolumeDialog == null) { forceTimeout(); return true; } @@ -461,7 +468,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie // never disable touch interactions for remote playback, the muting is not tied to // the state of the phone. sc.seekbarView.setEnabled(true); - } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) { + } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) || + (sConfirmSafeVolumeDialog != null)) { sc.seekbarView.setEnabled(false); } else { sc.seekbarView.setEnabled(true); @@ -490,7 +498,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } } - private void updateStates() { + public void updateStates() { final int count = mSliderGroup.getChildCount(); for (int i = 0; i < count; i++) { StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag(); @@ -562,9 +570,9 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie postMuteChanged(STREAM_MASTER, flags); } - public void postDisplaySafeVolumeWarning() { + public void postDisplaySafeVolumeWarning(int flags) { if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; - obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget(); + obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget(); } /** @@ -598,7 +606,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie removeMessages(MSG_FREE_RESOURCES); sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); - resetTimeout(); } @@ -704,7 +711,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) || (streamType != mAudioManager.getMasterStreamType() && streamType != AudioService.STREAM_REMOTE_MUSIC && - isMuted(streamType))) { + isMuted(streamType)) || + sConfirmSafeVolumeDialog != null) { sc.seekbarView.setEnabled(false); } else { sc.seekbarView.setEnabled(true); @@ -802,7 +810,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie removeMessages(MSG_FREE_RESOURCES); sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); - resetTimeout(); } @@ -838,30 +845,34 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } } - protected void onDisplaySafeVolumeWarning() { - synchronized (sConfirmSafeVolumeLock) { - if (sConfirmSafeVolumeDialog != null) { - return; + protected void onDisplaySafeVolumeWarning(int flags) { + if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) { + synchronized (sConfirmSafeVolumeLock) { + if (sConfirmSafeVolumeDialog != null) { + return; + } + sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext) + .setMessage(com.android.internal.R.string.safe_media_volume_warning) + .setPositiveButton(com.android.internal.R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mAudioService.disableSafeMediaVolume(); + } + }) + .setNegativeButton(com.android.internal.R.string.no, null) + .setIconAttribute(android.R.attr.alertDialogIcon) + .create(); + final WarningDialogReceiver warning = new WarningDialogReceiver(mContext, + sConfirmSafeVolumeDialog, this); + + sConfirmSafeVolumeDialog.setOnDismissListener(warning); + sConfirmSafeVolumeDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + sConfirmSafeVolumeDialog.show(); } - sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext) - .setMessage(com.android.internal.R.string.safe_media_volume_warning) - .setPositiveButton(com.android.internal.R.string.yes, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mAudioService.disableSafeMediaVolume(); - } - }) - .setNegativeButton(com.android.internal.R.string.no, null) - .setIconAttribute(android.R.attr.alertDialogIcon) - .create(); - final WarningDialogReceiver warning = new WarningDialogReceiver(mContext, - sConfirmSafeVolumeDialog); - - sConfirmSafeVolumeDialog.setOnDismissListener(warning); - sConfirmSafeVolumeDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - sConfirmSafeVolumeDialog.show(); + updateStates(); } + resetTimeout(); } /** @@ -957,6 +968,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie mDialog.dismiss(); mActiveStreamType = -1; } + synchronized (sConfirmSafeVolumeLock) { + if (sConfirmSafeVolumeDialog != null) { + sConfirmSafeVolumeDialog.dismiss(); + } + } break; } case MSG_RINGER_MODE_CHANGED: { @@ -980,7 +996,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie break; case MSG_DISPLAY_SAFE_VOLUME_WARNING: - onDisplaySafeVolumeWarning(); + onDisplaySafeVolumeWarning(msg.arg1); break; } } diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java deleted file mode 100644 index 7d16e14..0000000 --- a/core/java/android/view/WindowInfo.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.graphics.Rect; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Information the state of a window. - * - * @hide - */ -public class WindowInfo implements Parcelable { - - private static final int MAX_POOL_SIZE = 20; - - private static int UNDEFINED = -1; - - private static Object sPoolLock = new Object(); - private static WindowInfo sPool; - private static int sPoolSize; - - private WindowInfo mNext; - private boolean mInPool; - - public IBinder token; - - public final Rect frame = new Rect(); - - public final Rect touchableRegion = new Rect(); - - public int type = UNDEFINED; - - public float compatibilityScale = UNDEFINED; - - public boolean visible; - - public int displayId = UNDEFINED; - - public int layer = UNDEFINED; - - private WindowInfo() { - /* do nothing - reduce visibility */ - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeStrongBinder(token); - parcel.writeParcelable(frame, 0); - parcel.writeParcelable(touchableRegion, 0); - parcel.writeInt(type); - parcel.writeFloat(compatibilityScale); - parcel.writeInt(visible ? 1 : 0); - parcel.writeInt(displayId); - parcel.writeInt(layer); - recycle(); - } - - private void initFromParcel(Parcel parcel) { - token = parcel.readStrongBinder(); - frame.set((Rect) parcel.readParcelable(null)); - touchableRegion.set((Rect) parcel.readParcelable(null)); - type = parcel.readInt(); - compatibilityScale = parcel.readFloat(); - visible = (parcel.readInt() == 1); - displayId = parcel.readInt(); - layer = parcel.readInt(); - } - - public static WindowInfo obtain(WindowInfo other) { - WindowInfo info = obtain(); - info.token = other.token; - info.frame.set(other.frame); - info.touchableRegion.set(other.touchableRegion); - info.type = other.type; - info.compatibilityScale = other.compatibilityScale; - info.visible = other.visible; - info.displayId = other.displayId; - info.layer = other.layer; - return info; - } - - public static WindowInfo obtain() { - synchronized (sPoolLock) { - if (sPoolSize > 0) { - WindowInfo info = sPool; - sPool = info.mNext; - info.mNext = null; - info.mInPool = false; - sPoolSize--; - return info; - } else { - return new WindowInfo(); - } - } - } - - public void recycle() { - if (mInPool) { - throw new IllegalStateException("Already recycled."); - } - clear(); - synchronized (sPoolLock) { - if (sPoolSize < MAX_POOL_SIZE) { - mNext = sPool; - sPool = this; - mInPool = true; - sPoolSize++; - } - } - } - - private void clear() { - token = null; - frame.setEmpty(); - touchableRegion.setEmpty(); - type = UNDEFINED; - compatibilityScale = UNDEFINED; - visible = false; - displayId = UNDEFINED; - layer = UNDEFINED; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Window [token:").append((token != null) ? token.hashCode() : null); - builder.append(", displayId:").append(displayId); - builder.append(", type:").append(type); - builder.append(", visible:").append(visible); - builder.append(", layer:").append(layer); - builder.append(", compatibilityScale:").append(compatibilityScale); - builder.append(", frame:").append(frame); - builder.append(", touchableRegion:").append(touchableRegion); - builder.append("]"); - return builder.toString(); - } - - /** - * @see Parcelable.Creator - */ - public static final Parcelable.Creator<WindowInfo> CREATOR = - new Parcelable.Creator<WindowInfo>() { - public WindowInfo createFromParcel(Parcel parcel) { - WindowInfo info = WindowInfo.obtain(); - info.initFromParcel(parcel); - return info; - } - - public WindowInfo[] newArray(int size) { - return new WindowInfo[size]; - } - }; -} diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 26739b3..7bdb44c 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1112,14 +1112,6 @@ public interface WindowManagerPolicy { public void setLastInputMethodWindowLw(WindowState ime, WindowState target); /** - * Returns whether magnification can be applied to the given window type. - * - * @param attrs The window's LayoutParams. - * @return Whether magnification can be applied. - */ - public boolean canMagnifyWindowLw(WindowManager.LayoutParams attrs); - - /** * Called when the current user changes. Guaranteed to be called before the broadcast * of the new user id is made to all listeners. * diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 1500905..9603fe5 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -19,6 +19,7 @@ package android.view.accessibility; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Pools.SynchronizedPool; import java.util.ArrayList; import java.util.List; @@ -686,11 +687,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPES_ALL_MASK = 0xFFFFFFFF; private static final int MAX_POOL_SIZE = 10; - private static final Object sPoolLock = new Object(); - private static AccessibilityEvent sPool; - private static int sPoolSize; - private AccessibilityEvent mNext; - private boolean mIsInPool; + private static final SynchronizedPool<AccessibilityEvent> sPool = + new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE); private int mEventType; private CharSequence mPackageName; @@ -916,17 +914,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @return An instance. */ public static AccessibilityEvent obtain() { - synchronized (sPoolLock) { - if (sPool != null) { - AccessibilityEvent event = sPool; - sPool = sPool.mNext; - sPoolSize--; - event.mNext = null; - event.mIsInPool = false; - return event; - } - return new AccessibilityEvent(); - } + AccessibilityEvent event = sPool.acquire(); + return (event != null) ? event : new AccessibilityEvent(); } /** @@ -939,18 +928,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ @Override public void recycle() { - if (mIsInPool) { - throw new IllegalStateException("Event already recycled!"); - } clear(); - synchronized (sPoolLock) { - if (sPoolSize <= MAX_POOL_SIZE) { - mNext = sPool; - sPool = this; - mIsInPool = true; - sPoolSize++; - } - } + sPool.release(this); } /** @@ -1137,54 +1116,176 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @return The string representation. */ public static String eventTypeToString(int eventType) { - switch (eventType) { - case TYPE_VIEW_CLICKED: - return "TYPE_VIEW_CLICKED"; - case TYPE_VIEW_LONG_CLICKED: - return "TYPE_VIEW_LONG_CLICKED"; - case TYPE_VIEW_SELECTED: - return "TYPE_VIEW_SELECTED"; - case TYPE_VIEW_FOCUSED: - return "TYPE_VIEW_FOCUSED"; - case TYPE_VIEW_TEXT_CHANGED: - return "TYPE_VIEW_TEXT_CHANGED"; - case TYPE_WINDOW_STATE_CHANGED: - return "TYPE_WINDOW_STATE_CHANGED"; - case TYPE_VIEW_HOVER_ENTER: - return "TYPE_VIEW_HOVER_ENTER"; - case TYPE_VIEW_HOVER_EXIT: - return "TYPE_VIEW_HOVER_EXIT"; - case TYPE_NOTIFICATION_STATE_CHANGED: - return "TYPE_NOTIFICATION_STATE_CHANGED"; - case TYPE_TOUCH_EXPLORATION_GESTURE_START: - return "TYPE_TOUCH_EXPLORATION_GESTURE_START"; - case TYPE_TOUCH_EXPLORATION_GESTURE_END: - return "TYPE_TOUCH_EXPLORATION_GESTURE_END"; - case TYPE_WINDOW_CONTENT_CHANGED: - return "TYPE_WINDOW_CONTENT_CHANGED"; - case TYPE_VIEW_TEXT_SELECTION_CHANGED: - return "TYPE_VIEW_TEXT_SELECTION_CHANGED"; - case TYPE_VIEW_SCROLLED: - return "TYPE_VIEW_SCROLLED"; - case TYPE_ANNOUNCEMENT: - return "TYPE_ANNOUNCEMENT"; - case TYPE_VIEW_ACCESSIBILITY_FOCUSED: - return "TYPE_VIEW_ACCESSIBILITY_FOCUSED"; - case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: - return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED"; - case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: - return "TYPE_CURRENT_AT_GRANULARITY_MOVEMENT_CHANGED"; - case TYPE_GESTURE_DETECTION_START: - return "TYPE_GESTURE_DETECTION_START"; - case TYPE_GESTURE_DETECTION_END: - return "TYPE_GESTURE_DETECTION_END"; - case TYPE_TOUCH_INTERACTION_START: - return "TYPE_TOUCH_INTERACTION_START"; - case TYPE_TOUCH_INTERACTION_END: - return "TYPE_TOUCH_INTERACTION_END"; - default: - return null; + if (eventType == TYPES_ALL_MASK) { + return "TYPES_ALL_MASK"; } + StringBuilder builder = new StringBuilder(); + int eventTypeCount = 0; + while (eventType != 0) { + final int eventTypeFlag = 1 << Integer.numberOfTrailingZeros(eventType); + eventType &= ~eventTypeFlag; + switch (eventTypeFlag) { + case TYPE_VIEW_CLICKED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_CLICKED"); + eventTypeCount++; + } break; + case TYPE_VIEW_LONG_CLICKED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_LONG_CLICKED"); + eventTypeCount++; + } break; + case TYPE_VIEW_SELECTED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_SELECTED"); + eventTypeCount++; + } break; + case TYPE_VIEW_FOCUSED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_FOCUSED"); + eventTypeCount++; + } break; + case TYPE_VIEW_TEXT_CHANGED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_TEXT_CHANGED"); + eventTypeCount++; + } break; + case TYPE_WINDOW_STATE_CHANGED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_WINDOW_STATE_CHANGED"); + eventTypeCount++; + } break; + case TYPE_VIEW_HOVER_ENTER: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_HOVER_ENTER"); + eventTypeCount++; + } break; + case TYPE_VIEW_HOVER_EXIT: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_HOVER_EXIT"); + eventTypeCount++; + } break; + case TYPE_NOTIFICATION_STATE_CHANGED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_NOTIFICATION_STATE_CHANGED"); + eventTypeCount++; + } break; + case TYPE_TOUCH_EXPLORATION_GESTURE_START: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_START"); + eventTypeCount++; + } break; + case TYPE_TOUCH_EXPLORATION_GESTURE_END: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_END"); + eventTypeCount++; + } break; + case TYPE_WINDOW_CONTENT_CHANGED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_WINDOW_CONTENT_CHANGED"); + eventTypeCount++; + } break; + case TYPE_VIEW_TEXT_SELECTION_CHANGED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_TEXT_SELECTION_CHANGED"); + eventTypeCount++; + } break; + case TYPE_VIEW_SCROLLED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_SCROLLED"); + eventTypeCount++; + } break; + case TYPE_ANNOUNCEMENT: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_ANNOUNCEMENT"); + eventTypeCount++; + } break; + case TYPE_VIEW_ACCESSIBILITY_FOCUSED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUSED"); + eventTypeCount++; + } break; + case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED"); + eventTypeCount++; + } break; + case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_CURRENT_AT_GRANULARITY_MOVEMENT_CHANGED"); + eventTypeCount++; + } break; + case TYPE_GESTURE_DETECTION_START: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_GESTURE_DETECTION_START"); + eventTypeCount++; + } break; + case TYPE_GESTURE_DETECTION_END: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_GESTURE_DETECTION_END"); + eventTypeCount++; + } break; + case TYPE_TOUCH_INTERACTION_START: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_TOUCH_INTERACTION_START"); + eventTypeCount++; + } break; + case TYPE_TOUCH_INTERACTION_END: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_TOUCH_INTERACTION_END"); + eventTypeCount++; + } break; + } + } + if (eventTypeCount > 1) { + builder.insert(0, '['); + builder.append(']'); + } + return builder.toString(); } /** diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 20b5f17..67df684 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -17,7 +17,6 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; -import android.graphics.Rect; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -102,8 +101,6 @@ public final class AccessibilityInteractionClient private Message mSameThreadMessage; - private final Rect mTempBounds = new Rect(); - // The connection cache is shared between all interrogating threads. private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = new SparseArray<IAccessibilityServiceConnection>(); @@ -194,14 +191,14 @@ public final class AccessibilityInteractionClient return cachedInfo; } final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId( + final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( accessibilityWindowId, accessibilityNodeId, interactionId, this, prefetchFlags, Thread.currentThread().getId()); // If the scale is zero the call has failed. - if (windowScale > 0) { + if (success) { List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); if (infos != null && !infos.isEmpty()) { return infos.get(0); } @@ -242,15 +239,13 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = - connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId, - accessibilityNodeId, viewId, interactionId, this, - Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { + final boolean success =connection.findAccessibilityNodeInfoByViewId( + accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, + Thread.currentThread().getId()); + if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId); return info; } } else { @@ -290,14 +285,13 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findAccessibilityNodeInfosByText( + final boolean success = connection.findAccessibilityNodeInfosByText( accessibilityWindowId, accessibilityNodeId, text, interactionId, this, Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { + if (success) { List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); return infos; } } else { @@ -336,14 +330,13 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findFocus(accessibilityWindowId, + final boolean success = connection.findFocus(accessibilityWindowId, accessibilityNodeId, focusType, interactionId, this, Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { + if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId); return info; } } else { @@ -381,14 +374,13 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.focusSearch(accessibilityWindowId, + final boolean success = connection.focusSearch(accessibilityWindowId, accessibilityNodeId, direction, interactionId, this, Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { + if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId); return info; } } else { @@ -604,36 +596,14 @@ public final class AccessibilityInteractionClient } /** - * Applies compatibility scale to the info bounds if it is not equal to one. - * - * @param info The info whose bounds to scale. - * @param scale The scale to apply. - */ - private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info, float scale) { - if (scale == 1.0f) { - return; - } - Rect bounds = mTempBounds; - info.getBoundsInParent(bounds); - bounds.scale(scale); - info.setBoundsInParent(bounds); - - info.getBoundsInScreen(bounds); - bounds.scale(scale); - info.setBoundsInScreen(bounds); - } - - /** * Finalize an {@link AccessibilityNodeInfo} before passing it to the client. * * @param info The info. * @param connectionId The id of the connection to the system. - * @param windowScale The source window compatibility scale. */ - private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId, - float windowScale) { + private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, + int connectionId) { if (info != null) { - applyCompatibilityScaleIfNeeded(info, windowScale); info.setConnectionId(connectionId); info.setSealed(true); sAccessibilityNodeInfoCache.add(info); @@ -645,15 +615,14 @@ public final class AccessibilityInteractionClient * * @param infos The {@link AccessibilityNodeInfo}s. * @param connectionId The id of the connection to the system. - * @param windowScale The source window compatibility scale. */ private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos, - int connectionId, float windowScale) { + int connectionId) { if (infos != null) { final int infosCount = infos.size(); for (int i = 0; i < infosCount; i++) { AccessibilityNodeInfo info = infos.get(i); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId); } } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 1dc2487..6c03280 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -20,6 +20,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.Pools.SynchronizedPool; import android.util.SparseLongArray; import android.view.View; @@ -354,11 +355,9 @@ public class AccessibilityNodeInfo implements Parcelable { // Housekeeping. private static final int MAX_POOL_SIZE = 50; - private static final Object sPoolLock = new Object(); - private static AccessibilityNodeInfo sPool; - private static int sPoolSize; - private AccessibilityNodeInfo mNext; - private boolean mIsInPool; + private static final SynchronizedPool<AccessibilityNodeInfo> sPool = + new SynchronizedPool<AccessibilityNodeInfo>(MAX_POOL_SIZE); + private boolean mSealed; // Data. @@ -487,6 +486,31 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Refreshes this info with the latest state of the view it represents. + * <p> + * <strong>Note:</strong> If this method returns false this info is obsolete + * since it represents a view that is no longer in the view tree and should + * be recycled. + * </p> + * @return Whether the refresh succeeded. + */ + public boolean refresh() { + enforceSealed(); + if (!canPerformRequestOverConnection(mSourceNodeId)) { + return false; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId( + mConnectionId, mWindowId, mSourceNodeId, 0); + if (refreshedInfo == null) { + return false; + } + init(refreshedInfo); + refreshedInfo.recycle(); + return true; + } + + /** * @return The ids of the children. * * @hide @@ -1517,17 +1541,8 @@ public class AccessibilityNodeInfo implements Parcelable { * @return An instance. */ public static AccessibilityNodeInfo obtain() { - synchronized (sPoolLock) { - if (sPool != null) { - AccessibilityNodeInfo info = sPool; - sPool = sPool.mNext; - sPoolSize--; - info.mNext = null; - info.mIsInPool = false; - return info; - } - return new AccessibilityNodeInfo(); - } + AccessibilityNodeInfo info = sPool.acquire(); + return (info != null) ? info : new AccessibilityNodeInfo(); } /** @@ -1552,18 +1567,8 @@ public class AccessibilityNodeInfo implements Parcelable { * @throws IllegalStateException If the info is already recycled. */ public void recycle() { - if (mIsInPool) { - throw new IllegalStateException("Info already recycled!"); - } clear(); - synchronized (sPoolLock) { - if (sPoolSize <= MAX_POOL_SIZE) { - mNext = sPool; - sPool = this; - mIsInPool = true; - sPoolSize++; - } - } + sPool.release(this); } /** @@ -1620,7 +1625,6 @@ public class AccessibilityNodeInfo implements Parcelable { * * @param other The other instance. */ - @SuppressWarnings("unchecked") private void init(AccessibilityNodeInfo other) { mSealed = other.mSealed; mSourceNodeId = other.mSourceNodeId; diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index 9b39300..c313b07 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -17,6 +17,7 @@ package android.view.accessibility; import android.os.Bundle; +import android.view.MagnificationSpec; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; @@ -30,23 +31,23 @@ oneway interface IAccessibilityInteractionConnection { void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid); + long interrogatingTid, in MagnificationSpec spec); void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid); + long interrogatingTid, in MagnificationSpec spec); void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid); + long interrogatingTid, in MagnificationSpec spec); void findFocus(long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid); + long interrogatingTid, in MagnificationSpec spec); void focusSearch(long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid); + long interrogatingTid, in MagnificationSpec spec); void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 4dbca23..6fb8bdf 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -226,8 +226,6 @@ class BrowserFrame extends Handler { } else { sJavaBridge.setCacheSize(4 * 1024 * 1024); } - // initialize CacheManager - CacheManager.init(appContext); // create CookieSyncManager with current Context CookieSyncManager.createInstance(appContext); // create PluginManager with current Context diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index 52f41e6..bbd3f2b 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -45,14 +45,6 @@ import java.util.Map; // CacheManager may only be used if your activity contains a WebView. @Deprecated public final class CacheManager { - - private static final String LOGTAG = "cache"; - - static final String HEADER_KEY_IFMODIFIEDSINCE = "if-modified-since"; - static final String HEADER_KEY_IFNONEMATCH = "if-none-match"; - - private static File mBaseDir; - /** * Represents a resource stored in the HTTP cache. Instances of this class * can be obtained by calling @@ -239,39 +231,23 @@ public final class CacheManager { } /** - * Initializes the HTTP cache. This method must be called before any - * CacheManager methods are used. Note that this is called automatically - * when a {@link WebView} is created. - * - * @param context the application context - */ - static void init(Context context) { - // This isn't actually where the real cache lives, but where we put files for the - // purpose of getCacheFile(). - mBaseDir = new File(context.getCacheDir(), "webviewCacheChromiumStaging"); - if (!mBaseDir.exists()) { - mBaseDir.mkdirs(); - } - } - - /** * Gets the base directory in which the files used to store the contents of * cache entries are placed. See * {@link CacheManager.CacheResult#getLocalPath CacheManager.CacheResult.getLocalPath()}. * * @return the base directory of the cache - * @deprecated Access to the HTTP cache will be removed in a future release. + * @deprecated This method no longer has any effect and always returns null. */ @Deprecated public static File getCacheFileBaseDir() { - return mBaseDir; + return null; } /** * Gets whether the HTTP cache is disabled. * * @return true if the HTTP cache is disabled - * @deprecated Access to the HTTP cache will be removed in a future release. + * @deprecated This method no longer has any effect and always returns false. */ @Deprecated public static boolean cacheDisabled() { @@ -314,73 +290,11 @@ public final class CacheManager { * @param headers a map from HTTP header name to value, to be populated * for the returned cache entry * @return the cache entry for the specified URL - * @deprecated Access to the HTTP cache will be removed in a future release. + * @deprecated This method no longer has any effect and always returns null. */ @Deprecated public static CacheResult getCacheFile(String url, Map<String, String> headers) { - return getCacheFile(url, 0, headers); - } - - static CacheResult getCacheFile(String url, long postIdentifier, - Map<String, String> headers) { - CacheResult result = nativeGetCacheResult(url); - if (result == null) { - return null; - } - // A temporary local file will have been created native side and localPath set - // appropriately. - File src = new File(mBaseDir, result.localPath); - try { - // Open the file here so that even if it is deleted, the content - // is still readable by the caller until close() is called. - result.inStream = new FileInputStream(src); - } catch (FileNotFoundException e) { - Log.v(LOGTAG, "getCacheFile(): Failed to open file: " + e); - // TODO: The files in the cache directory can be removed by the - // system. If it is gone, what should we do? - return null; - } - - // A null value for headers is used by CACHE_MODE_CACHE_ONLY to imply - // that we should provide the cache result even if it is expired. - // Note that a negative expires value means a time in the far future. - if (headers != null && result.expires >= 0 - && result.expires <= System.currentTimeMillis()) { - if (result.lastModified == null && result.etag == null) { - return null; - } - // Return HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE - // for requesting validation. - if (result.etag != null) { - headers.put(HEADER_KEY_IFNONEMATCH, result.etag); - } - if (result.lastModified != null) { - headers.put(HEADER_KEY_IFMODIFIEDSINCE, result.lastModified); - } - } - - if (DebugFlags.CACHE_MANAGER) { - Log.v(LOGTAG, "getCacheFile for url " + url); - } - - return result; - } - - /** - * Given a URL and its full headers, gets a CacheResult if a local cache - * can be stored. Otherwise returns null. The mimetype is passed in so that - * the function can use the mimetype that will be passed to WebCore which - * could be different from the mimetype defined in the headers. - * forceCache is for out-of-package callers to force creation of a - * CacheResult, and is used to supply surrogate responses for URL - * interception. - * - * @return a CacheResult for a given URL - */ - static CacheResult createCacheFile(String url, int statusCode, - Headers headers, String mimeType, boolean forceCache) { - // This method is public but hidden. We break functionality. return null; } @@ -424,36 +338,4 @@ public final class CacheManager { // use, we should already have thrown an exception above. assert false; } - - /** - * Removes all cache files. - * - * @return whether the removal succeeded - */ - static boolean removeAllCacheFiles() { - // delete cache files in a separate thread to not block UI. - final Runnable clearCache = new Runnable() { - public void run() { - // delete all cache files - try { - String[] files = mBaseDir.list(); - // if mBaseDir doesn't exist, files can be null. - if (files != null) { - for (int i = 0; i < files.length; i++) { - File f = new File(mBaseDir, files[i]); - if (!f.delete()) { - Log.e(LOGTAG, f.getPath() + " delete failed."); - } - } - } - } catch (SecurityException e) { - // Ignore SecurityExceptions. - } - } - }; - new Thread(clearCache).start(); - return true; - } - - private static native CacheResult nativeGetCacheResult(String url); } diff --git a/core/java/android/webkit/EventLogTags.logtags b/core/java/android/webkit/EventLogTags.logtags index 082a437..b0b5493 100644 --- a/core/java/android/webkit/EventLogTags.logtags +++ b/core/java/android/webkit/EventLogTags.logtags @@ -8,4 +8,3 @@ option java_package android.webkit; # 70103- used by the browser app itself 70150 browser_snap_center -70151 browser_text_size_change (oldSize|1|5), (newSize|1|5) diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java index 296d960..ee3b369 100644 --- a/core/java/android/webkit/HttpAuthHandler.java +++ b/core/java/android/webkit/HttpAuthHandler.java @@ -40,7 +40,7 @@ public class HttpAuthHandler extends Handler { * previously been rejected by the server for the current request. * * @return whether the credentials are suitable for use - * @see Webview#getHttpAuthUsernamePassword + * @see WebView#getHttpAuthUsernamePassword */ public boolean useHttpAuthUsernamePassword() { return false; diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index aa68904..83f7990 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -89,6 +89,14 @@ public abstract class WebSettings { ZoomDensity(int size) { value = size; } + + /** + * @hide Only for use by WebViewProvider implementations + */ + public int getValue() { + return value; + } + int value; } @@ -936,6 +944,9 @@ public abstract class WebSettings { * access to content from other file scheme URLs. See * {@link #setAllowFileAccessFromFileURLs}. To enable the most restrictive, * and therefore secure policy, this setting should be disabled. + * Note that this setting affects only JavaScript access to file scheme + * resources. Other access to such resources, for example, from image HTML + * elements, is unaffected. * <p> * The default value is true for API level * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, @@ -953,6 +964,9 @@ public abstract class WebSettings { * enable the most restrictive, and therefore secure policy, this setting * should be disabled. Note that the value of this setting is ignored if * the value of {@link #getAllowUniversalAccessFromFileURLs} is true. + * Note too, that this setting affects only JavaScript access to file scheme + * resources. Other access to such resources, for example, from image HTML + * elements, is unaffected. * <p> * The default value is true for API level * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, @@ -1052,7 +1066,7 @@ public abstract class WebSettings { * * @param appCachePath a String path to the directory containing * Application Caches files. - * @see setAppCacheEnabled + * @see #setAppCacheEnabled */ public synchronized void setAppCachePath(String appCachePath) { throw new MustOverrideException(); @@ -1121,9 +1135,22 @@ public abstract class WebSettings { } /** - * Sets whether Geolocation is enabled. The default is true. See also - * {@link #setGeolocationDatabasePath} for how to correctly set up - * Geolocation. + * Sets whether Geolocation is enabled. The default is true. + * <p> + * Please note that in order for the Geolocation API to be usable + * by a page in the WebView, the following requirements must be met: + * <ul> + * <li>an application must have permission to access the device location, + * see {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}, + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}; + * <li>an application must provide an implementation of the + * {@link WebChromeClient#onGeolocationPermissionsShowPrompt} callback + * to receive notifications that a page is requesting access to location + * via the JavaScript Geolocation API. + * </ul> + * <p> + * As an option, it is possible to store previous locations and web origin + * permissions in a database. See {@link #setGeolocationDatabasePath}. * * @param flag whether Geolocation should be enabled */ @@ -1295,7 +1322,7 @@ public abstract class WebSettings { * and content is re-validated as needed. When navigating back, content is * not revalidated, instead the content is just retrieved from the cache. * This method allows the client to override this behavior by specifying - * one of {@link #LOAD_DEFAULT}, {@link #LOAD_NORMAL}, + * one of {@link #LOAD_DEFAULT}, * {@link #LOAD_CACHE_ELSE_NETWORK}, {@link #LOAD_NO_CACHE} or * {@link #LOAD_CACHE_ONLY}. The default value is {@link #LOAD_DEFAULT}. * diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index 1bbe7bb..e3d095f 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -647,10 +647,6 @@ public class WebSettingsClassic extends WebSettings { @Override public synchronized void setTextZoom(int textZoom) { if (mTextSize != textZoom) { - if (WebViewClassic.mLogEvent) { - EventLog.writeEvent(EventLogTags.BROWSER_TEXT_SIZE_CHANGE, - mTextSize, textZoom); - } mTextSize = textZoom; postSync(); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 6df7820..441b7b0 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -615,7 +615,7 @@ public class WebView extends AbsoluteLayout * @param realm the realm to which the credentials apply * @param username the username * @param password the password - * @see getHttpAuthUsernamePassword + * @see #getHttpAuthUsernamePassword * @see WebViewDatabase#hasHttpAuthUsernamePassword * @see WebViewDatabase#clearHttpAuthUsernamePassword */ @@ -635,7 +635,7 @@ public class WebView extends AbsoluteLayout * @return the credentials as a String array, if found. The first element * is the username and the second element is the password. Null if * no credentials are found. - * @see setHttpAuthUsernamePassword + * @see #setHttpAuthUsernamePassword * @see WebViewDatabase#hasHttpAuthUsernamePassword * @see WebViewDatabase#clearHttpAuthUsernamePassword */ @@ -2137,4 +2137,10 @@ public class WebView extends AbsoluteLayout super.setLayerType(layerType, paint); mProvider.getViewDelegate().setLayerType(layerType, paint); } + + @Override + protected void dispatchDraw(Canvas canvas) { + mProvider.getViewDelegate().preDispatchDraw(canvas); + super.dispatchDraw(canvas); + } } diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index ae56e6b..9a83964 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -8560,6 +8560,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc updateHwAccelerated(); } + @Override + public void preDispatchDraw(Canvas canvas) { + // no-op for WebViewClassic. + } + private void updateHwAccelerated() { if (mNativeClass == 0) { return; diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 08a046a..e8974c6 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -31,6 +31,7 @@ public class WebViewClient { * proper handler for the url. If WebViewClient is provided, return true * means the host application handles the url, while return false means the * current WebView handles the url. + * This method is not called for requests using the POST "method". * * @param view The WebView that is initiating the callback. * @param url The url to be loaded. @@ -82,9 +83,9 @@ public class WebViewClient { * Notify the host application of a resource request and allow the * application to return the data. If the return value is null, the WebView * will continue to load the resource as usual. Otherwise, the return - * response and data will be used. NOTE: This method is called by the - * network thread so clients should exercise caution when accessing private - * data. + * response and data will be used. NOTE: This method is called on a thread + * other than the UI thread so clients should exercise caution + * when accessing private data or the view system. * * @param view The {@link android.webkit.WebView} that is requesting the * resource. @@ -213,7 +214,7 @@ public class WebViewClient { * @param handler the HttpAuthHandler used to set the WebView's response * @param host the host requiring authentication * @param realm the realm for which authentication is required - * @see Webview#getHttpAuthUsernamePassword + * @see WebView#getHttpAuthUsernamePassword */ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 3fb3ec6..9e5a326 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -2001,9 +2001,6 @@ public final class WebViewCore { private void clearCache(boolean includeDiskFiles) { mBrowserFrame.clearCache(); - if (includeDiskFiles) { - CacheManager.removeAllCacheFiles(); - } } private void loadUrl(String url, Map<String, String> extraHeaders) { diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 5597259..e08052a 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -50,7 +50,7 @@ public class WebViewDatabase { * * @return true if there are any saved username/password pairs * @see WebView#savePassword - * @see clearUsernamePassword + * @see #clearUsernamePassword */ public boolean hasUsernamePassword() { throw new MustOverrideException(); @@ -61,7 +61,7 @@ public class WebViewDatabase { * Note that these are unrelated to HTTP authentication credentials. * * @see WebView#savePassword - * @see hasUsernamePassword + * @see #hasUsernamePassword */ public void clearUsernamePassword() { throw new MustOverrideException(); @@ -71,9 +71,9 @@ public class WebViewDatabase { * Gets whether there are any saved credentials for HTTP authentication. * * @return whether there are any saved credentials - * @see Webview#getHttpAuthUsernamePassword - * @see Webview#setHttpAuthUsernamePassword - * @see clearHttpAuthUsernamePassword + * @see WebView#getHttpAuthUsernamePassword + * @see WebView#setHttpAuthUsernamePassword + * @see #clearHttpAuthUsernamePassword */ public boolean hasHttpAuthUsernamePassword() { throw new MustOverrideException(); @@ -82,9 +82,9 @@ public class WebViewDatabase { /** * Clears any saved credentials for HTTP authentication. * - * @see Webview#getHttpAuthUsernamePassword - * @see Webview#setHttpAuthUsernamePassword - * @see hasHttpAuthUsernamePassword + * @see WebView#getHttpAuthUsernamePassword + * @see WebView#setHttpAuthUsernamePassword + * @see #hasHttpAuthUsernamePassword */ public void clearHttpAuthUsernamePassword() { throw new MustOverrideException(); @@ -94,7 +94,7 @@ public class WebViewDatabase { * Gets whether there is any saved data for web forms. * * @return whether there is any saved data for web forms - * @see clearFormData + * @see #clearFormData */ public boolean hasFormData() { throw new MustOverrideException(); @@ -103,7 +103,7 @@ public class WebViewDatabase { /** * Clears any saved data for web forms. * - * @see hasFormData + * @see #hasFormData */ public void clearFormData() { throw new MustOverrideException(); diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index b833a01..18df0b1 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -31,7 +31,7 @@ class WebViewFactory { // TODO: When the Chromium powered WebView is ready, it should be the default factory class. private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory"; private static final String CHROMIUM_WEBVIEW_FACTORY = - "com.android.webviewchromium.WebViewChromiumFactoryProvider"; + "com.android.webview.chromium.WebViewChromiumFactoryProvider"; private static final String CHROMIUM_WEBVIEW_JAR = "/system/framework/webviewchromium.jar"; private static final String LOGTAG = "WebViewFactory"; diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index c9f9fbd..1020634 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -341,6 +341,8 @@ public interface WebViewProvider { public void setBackgroundColor(int color); public void setLayerType(int layerType, Paint paint); + + public void preDispatchDraw(Canvas canvas); } interface ScrollDelegate { diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java index f279f8e..a379157 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -375,7 +375,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { /** * Constructor called from {@link #CREATOR} */ - private SavedState(Parcel in) { + SavedState(Parcel in) { super(in); selectedId = in.readLong(); position = in.readInt(); diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java index f1804f8..41ab5f2 100644 --- a/core/java/android/widget/CheckBox.java +++ b/core/java/android/widget/CheckBox.java @@ -20,6 +20,7 @@ import android.content.Context; import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.util.ValueModel; /** @@ -55,7 +56,9 @@ import android.view.accessibility.AccessibilityNodeInfo; * {@link android.R.styleable#View View Attributes} * </p> */ -public class CheckBox extends CompoundButton { +public class CheckBox extends CompoundButton implements ValueEditor<Boolean> { + private ValueModel<Boolean> mValueModel = ValueModel.EMPTY; + public CheckBox(Context context) { this(context, null); } @@ -79,4 +82,22 @@ public class CheckBox extends CompoundButton { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(CheckBox.class.getName()); } + + @Override + public ValueModel<Boolean> getValueModel() { + return mValueModel; + } + + @Override + public void setValueModel(ValueModel<Boolean> valueModel) { + mValueModel = valueModel; + setChecked(mValueModel.get()); + } + + @Override + public boolean performClick() { + boolean handled = super.performClick(); + mValueModel.set(isChecked()); + return handled; + } } diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 57e51c2..ec81214 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -17,6 +17,7 @@ package android.widget; import android.content.Context; +import android.graphics.Rect; import android.text.Editable; import android.text.Selection; import android.text.Spannable; @@ -24,6 +25,7 @@ import android.text.TextUtils; import android.text.method.ArrowKeyMovementMethod; import android.text.method.MovementMethod; import android.util.AttributeSet; +import android.util.ValueModel; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -47,7 +49,9 @@ import android.view.accessibility.AccessibilityNodeInfo; * {@link android.R.styleable#TextView TextView Attributes}, * {@link android.R.styleable#View View Attributes} */ -public class EditText extends TextView { +public class EditText extends TextView implements ValueEditor<CharSequence> { + private ValueModel<CharSequence> mValueModel = ValueModel.EMPTY; + public EditText(Context context) { this(context, null); } @@ -128,4 +132,21 @@ public class EditText extends TextView { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(EditText.class.getName()); } + + @Override + public ValueModel<CharSequence> getValueModel() { + return mValueModel; + } + + @Override + public void setValueModel(ValueModel<CharSequence> valueModel) { + mValueModel = valueModel; + setText(mValueModel.get()); + } + + @Override + void sendAfterTextChanged(Editable text) { + super.sendAfterTextChanged(text); + mValueModel.set(text); + } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 85972c3..217bedb 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2638,15 +2638,10 @@ public class Editor { suggestionStart, suggestionEnd).toString(); mTextView.replaceText_internal(spanStart, spanEnd, suggestion); - // Notify source IME of the suggestion pick. Do this before swaping texts. - if (!TextUtils.isEmpty( - suggestionInfo.suggestionSpan.getNotificationTargetClassName())) { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText, - suggestionInfo.suggestionIndex); - } - } + // Notify source IME of the suggestion pick. Do this before + // swaping texts. + suggestionInfo.suggestionSpan.notifySelection( + mTextView.getContext(), originalText, suggestionInfo.suggestionIndex); // Swap text content between actual text and Suggestion span String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions(); diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 772d748..85ed8db 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -605,7 +605,7 @@ public class GridLayout extends ViewGroup { } private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) { - return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, horizontal, leading); + return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading); } private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) { @@ -824,13 +824,11 @@ public class GridLayout extends ViewGroup { // Draw grid private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) { - int dx = getPaddingLeft(); - int dy = getPaddingTop(); if (isLayoutRtl()) { int width = getWidth(); - graphics.drawLine(width - dx - x1, dy + y1, width - dx - x2, dy + y2, paint); + graphics.drawLine(width - x1, y1, width - x2, y2, paint); } else { - graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint); + graphics.drawLine(x1, y1, x2, y2, paint); } } @@ -838,18 +836,17 @@ public class GridLayout extends ViewGroup { * @hide */ @Override - protected void onDebugDrawMargins(Canvas canvas) { + protected void onDebugDrawMargins(Canvas canvas, Paint paint) { // Apply defaults, so as to remove UNDEFINED values LayoutParams lp = new LayoutParams(); for (int i = 0; i < getChildCount(); i++) { View c = getChildAt(i); - Insets insets = getLayoutMode() == OPTICAL_BOUNDS ? c.getOpticalInsets() : Insets.NONE; lp.setMargins( - getMargin1(c, true, true) - insets.left, - getMargin1(c, false, true) - insets.top, - getMargin1(c, true, false) - insets.right, - getMargin1(c, false, false) - insets.bottom); - lp.onDebugDraw(c, canvas); + getMargin1(c, true, true), + getMargin1(c, false, true), + getMargin1(c, true, false), + getMargin1(c, false, false)); + lp.onDebugDraw(c, canvas, paint); } } @@ -858,26 +855,30 @@ public class GridLayout extends ViewGroup { */ @Override protected void onDebugDraw(Canvas canvas) { - int height = getHeight() - getPaddingTop() - getPaddingBottom(); - int width = getWidth() - getPaddingLeft() - getPaddingRight(); - Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.argb(50, 255, 255, 255)); + Insets insets = getOpticalInsets(); + + int top = getPaddingTop() + insets.top; + int left = getPaddingLeft() + insets.left; + int right = getWidth() - getPaddingRight() - insets.right; + int bottom = getHeight() - getPaddingBottom() - insets.bottom; + int[] xs = horizontalAxis.locations; if (xs != null) { for (int i = 0, length = xs.length; i < length; i++) { - int x = xs[i]; - drawLine(canvas, x, 0, x, height - 1, paint); + int x = left + xs[i]; + drawLine(canvas, x, top, x, bottom, paint); } } int[] ys = verticalAxis.locations; if (ys != null) { for (int i = 0, length = ys.length; i < length; i++) { - int y = ys[i]; - drawLine(canvas, 0, y, width - 1, y, paint); + int y = top + ys[i]; + drawLine(canvas, left, y, right, y, paint); } } @@ -1013,12 +1014,7 @@ public class GridLayout extends ViewGroup { } private int getMeasurement(View c, boolean horizontal) { - int result = horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); - if (getLayoutMode() == OPTICAL_BOUNDS) { - Insets insets = c.getOpticalInsets(); - return result - (horizontal ? insets.left + insets.right : insets.top + insets.bottom); - } - return result; + return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); } final int getMeasurementIncludingMargin(View c, boolean horizontal) { @@ -1124,14 +1120,6 @@ public class GridLayout extends ViewGroup { targetWidth - width - paddingRight - rightMargin - dx; int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin; - boolean useLayoutBounds = getLayoutMode() == OPTICAL_BOUNDS; - if (useLayoutBounds) { - Insets insets = c.getOpticalInsets(); - cx -= insets.left; - cy -= insets.top; - width += (insets.left + insets.right); - height += (insets.top + insets.bottom); - } if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) { c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); } @@ -2418,6 +2406,8 @@ public class GridLayout extends ViewGroup { * <li> {@code spec.span = [start, start + size]} </li> * <li> {@code spec.alignment = alignment} </li> * </ul> + * <p> + * To leave the start index undefined, use the value {@link #UNDEFINED}. * * @param start the start * @param size the size @@ -2433,9 +2423,13 @@ public class GridLayout extends ViewGroup { * <li> {@code spec.span = [start, start + 1]} </li> * <li> {@code spec.alignment = alignment} </li> * </ul> + * <p> + * To leave the start index undefined, use the value {@link #UNDEFINED}. * * @param start the start index * @param alignment the alignment + * + * @see #spec(int, int, Alignment) */ public static Spec spec(int start, Alignment alignment) { return spec(start, 1, alignment); @@ -2446,9 +2440,13 @@ public class GridLayout extends ViewGroup { * <ul> * <li> {@code spec.span = [start, start + size]} </li> * </ul> + * <p> + * To leave the start index undefined, use the value {@link #UNDEFINED}. * * @param start the start * @param size the size + * + * @see #spec(int, Alignment) */ public static Spec spec(int start, int size) { return spec(start, size, UNDEFINED_ALIGNMENT); @@ -2459,8 +2457,12 @@ public class GridLayout extends ViewGroup { * <ul> * <li> {@code spec.span = [start, start + 1]} </li> * </ul> + * <p> + * To leave the start index undefined, use the value {@link #UNDEFINED}. * * @param start the start index + * + * @see #spec(int, int) */ public static Spec spec(int start) { return spec(start, 1); @@ -2654,14 +2656,7 @@ public class GridLayout extends ViewGroup { @Override public int getAlignmentValue(View view, int viewSize, int mode) { int baseline = view.getBaseline(); - if (baseline == -1) { - return UNDEFINED; - } else { - if (mode == OPTICAL_BOUNDS) { - return baseline - view.getOpticalInsets().top; - } - return baseline; - } + return baseline == -1 ? UNDEFINED : baseline; } @Override diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 87396fb..b40260c 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -30,6 +30,7 @@ import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; @@ -40,6 +41,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; +import java.io.IOException; +import java.io.InputStream; + /** * Displays an arbitrary image, such as an icon. The ImageView class * can load images from various sources (such as resources or content @@ -90,6 +94,9 @@ public class ImageView extends View { private int mBaseline = -1; private boolean mBaselineAlignBottom = false; + // AdjustViewBounds behavior will be in compatibility mode for older apps. + private boolean mAdjustViewBoundsCompat = false; + private static final ScaleType[] sScaleTypeArray = { ScaleType.MATRIX, ScaleType.FIT_XY, @@ -164,6 +171,8 @@ public class ImageView extends View { private void initImageView() { mMatrix = new Matrix(); mScaleType = ScaleType.FIT_CENTER; + mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <= + Build.VERSION_CODES.JELLY_BEAN_MR1; } @Override @@ -225,8 +234,15 @@ public class ImageView extends View { /** * Set this to true if you want the ImageView to adjust its bounds * to preserve the aspect ratio of its drawable. + * + * <p><strong>Note:</strong> If the application targets API level 17 or lower, + * adjustViewBounds will allow the drawable to shrink the view bounds, but not grow + * to fill available measured space in all cases. This is for compatibility with + * legacy {@link android.view.View.MeasureSpec MeasureSpec} and + * {@link android.widget.RelativeLayout RelativeLayout} behavior.</p> + * * @param adjustViewBounds Whether to adjust the bounds of this view - * to presrve the original aspect ratio of the drawable + * to preserve the original aspect ratio of the drawable. * * @see #getAdjustViewBounds() * @@ -632,20 +648,27 @@ public class ImageView extends View { } } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) || ContentResolver.SCHEME_FILE.equals(scheme)) { + InputStream stream = null; try { - d = Drawable.createFromStream( - mContext.getContentResolver().openInputStream(mUri), - null); + stream = mContext.getContentResolver().openInputStream(mUri); + d = Drawable.createFromStream(stream, null); } catch (Exception e) { Log.w("ImageView", "Unable to open content: " + mUri, e); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Log.w("ImageView", "Unable to close content: " + mUri, e); + } + } } - } else { + } else { d = Drawable.createFromPath(mUri.toString()); } if (d == null) { - System.out.println("resolveUri failed on bad bitmap uri: " - + mUri); + System.out.println("resolveUri failed on bad bitmap uri: " + mUri); // Don't try again. mUri = null; } @@ -789,6 +812,12 @@ public class ImageView extends View { if (resizeWidth) { int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright; + + // Allow the width to outgrow its original estimate if height is fixed. + if (!resizeHeight && !mAdjustViewBoundsCompat) { + widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); + } + if (newWidth <= widthSize) { widthSize = newWidth; done = true; @@ -799,6 +828,13 @@ public class ImageView extends View { if (!done && resizeHeight) { int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom; + + // Allow the height to outgrow its original estimate if width is fixed. + if (!resizeWidth && !mAdjustViewBoundsCompat) { + heightSize = resolveAdjustedSize(newHeight, mMaxHeight, + heightMeasureSpec); + } + if (newHeight <= heightSize) { heightSize = newHeight; } diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 36dd13c..bc57c36 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -1431,9 +1431,9 @@ public class LinearLayout extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { - layoutVertical(); + layoutVertical(l, t, r, b); } else { - layoutHorizontal(); + layoutHorizontal(l, t, r, b); } } @@ -1444,15 +1444,19 @@ public class LinearLayout extends ViewGroup { * @see #getOrientation() * @see #setOrientation(int) * @see #onLayout(boolean, int, int, int, int) + * @param left + * @param top + * @param right + * @param bottom */ - void layoutVertical() { + void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // Where right end of child should go - final int width = mRight - mLeft; + final int width = right - left; int childRight = width - mPaddingRight; // Space available for child @@ -1466,12 +1470,12 @@ public class LinearLayout extends ViewGroup { switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already - childTop = mPaddingTop + mBottom - mTop - mTotalLength; + childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: - childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2; + childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: @@ -1534,8 +1538,12 @@ public class LinearLayout extends ViewGroup { * @see #getOrientation() * @see #setOrientation(int) * @see #onLayout(boolean, int, int, int, int) + * @param left + * @param top + * @param right + * @param bottom */ - void layoutHorizontal() { + void layoutHorizontal(int left, int top, int right, int bottom) { final boolean isLayoutRtl = isLayoutRtl(); final int paddingTop = mPaddingTop; @@ -1543,7 +1551,7 @@ public class LinearLayout extends ViewGroup { int childLeft; // Where bottom of child should go - final int height = mBottom - mTop; + final int height = bottom - top; int childBottom = height - mPaddingBottom; // Space available for child @@ -1563,12 +1571,12 @@ public class LinearLayout extends ViewGroup { switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) { case Gravity.RIGHT: // mTotalLength contains the padding already - childLeft = mPaddingLeft + mRight - mLeft - mTotalLength; + childLeft = mPaddingLeft + right - left - mTotalLength; break; case Gravity.CENTER_HORIZONTAL: // mTotalLength contains the padding already - childLeft = mPaddingLeft + (mRight - mLeft - mTotalLength) / 2; + childLeft = mPaddingLeft + (right - left - mTotalLength) / 2; break; case Gravity.LEFT: diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index f76ab2b..ee1bf18 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -149,7 +149,7 @@ public class MediaController extends FrameLayout { private void initFloatingWindowLayout() { mDecorLayoutParams = new WindowManager.LayoutParams(); WindowManager.LayoutParams p = mDecorLayoutParams; - p.gravity = Gravity.TOP; + p.gravity = Gravity.TOP | Gravity.LEFT; p.height = LayoutParams.WRAP_CONTENT; p.x = 0; p.format = PixelFormat.TRANSLUCENT; @@ -167,9 +167,15 @@ public class MediaController extends FrameLayout { int [] anchorPos = new int[2]; mAnchor.getLocationOnScreen(anchorPos); + // we need to know the size of the controller so we can properly position it + // within its space + mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST)); + WindowManager.LayoutParams p = mDecorLayoutParams; p.width = mAnchor.getWidth(); - p.y = anchorPos[1] + mAnchor.getHeight(); + p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2; + p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight(); } // This is called whenever mAnchor's layout bound changes @@ -204,6 +210,8 @@ public class MediaController extends FrameLayout { /** * Set the view that acts as the anchor for the control view. * This can for example be a VideoView, or your Activity's main view. + * When VideoView calls this method, it will use the VideoView's parent + * as the anchor. * @param view The view to which to anchor the controller when it is visible. */ public void setAnchorView(View view) { diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index ea50e2e..f2d2c65 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -38,10 +38,7 @@ import android.graphics.drawable.shapes.Shape; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; -import android.util.Pool; -import android.util.Poolable; -import android.util.PoolableManager; -import android.util.Pools; +import android.util.Pools.SynchronizedPool; import android.view.Gravity; import android.view.RemotableViewMethod; import android.view.View; @@ -604,33 +601,20 @@ public class ProgressBar extends View { } } - private static class RefreshData implements Poolable<RefreshData> { + private static class RefreshData { + private static final int POOL_MAX = 24; + private static final SynchronizedPool<RefreshData> sPool = + new SynchronizedPool<RefreshData>(POOL_MAX); + public int id; public int progress; public boolean fromUser; - - private RefreshData mNext; - private boolean mIsPooled; - - private static final int POOL_MAX = 24; - private static final Pool<RefreshData> sPool = Pools.synchronizedPool( - Pools.finitePool(new PoolableManager<RefreshData>() { - @Override - public RefreshData newInstance() { - return new RefreshData(); - } - - @Override - public void onAcquired(RefreshData element) { - } - - @Override - public void onReleased(RefreshData element) { - } - }, POOL_MAX)); public static RefreshData obtain(int id, int progress, boolean fromUser) { RefreshData rd = sPool.acquire(); + if (rd == null) { + rd = new RefreshData(); + } rd.id = id; rd.progress = progress; rd.fromUser = fromUser; @@ -640,28 +624,8 @@ public class ProgressBar extends View { public void recycle() { sPool.release(this); } - - @Override - public void setNextPoolable(RefreshData element) { - mNext = element; - } - - @Override - public RefreshData getNextPoolable() { - return mNext; - } - - @Override - public boolean isPooled() { - return mIsPooled; - } - - @Override - public void setPooled(boolean isPooled) { - mIsPooled = isPooled; - } } - + private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, boolean callBackToApp) { float scale = mMax > 0 ? (float) progress / (float) mMax : 0; diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 27fda24..e749e63 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -29,11 +29,9 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; +import android.os.Build; import android.util.AttributeSet; -import android.util.Pool; -import android.util.Poolable; -import android.util.PoolableManager; -import android.util.Pools; +import android.util.Pools.SimplePool; import android.util.SparseArray; import android.view.Gravity; import android.view.View; @@ -56,6 +54,21 @@ import static android.util.Log.d; * {@link #ALIGN_PARENT_BOTTOM}. * </p> * + * <p><strong>Note:</strong> In platform version 17 and lower, RelativeLayout was affected by + * a measurement bug that could cause child views to be measured with incorrect + * {@link android.view.View.MeasureSpec MeasureSpec} values. (See + * {@link android.view.View.MeasureSpec#makeMeasureSpec(int, int) MeasureSpec.makeMeasureSpec} + * for more details.) This was triggered when a RelativeLayout container was placed in + * a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view + * not equipped to properly measure with the MeasureSpec mode + * {@link android.view.View.MeasureSpec#UNSPECIFIED UNSPECIFIED} was placed in a RelativeLayout, + * this would silently work anyway as RelativeLayout would pass a very large + * {@link android.view.View.MeasureSpec#AT_MOST AT_MOST} MeasureSpec instead.</p> + * + * <p>This behavior has been preserved for apps that set <code>android:targetSdkVersion="17"</code> + * or older in their manifest's <code>uses-sdk</code> tag for compatibility. Apps targeting SDK + * version 18 or newer will receive the correct behavior</p> + * * <p>See the <a href="{@docRoot}guide/topics/ui/layout/relative.html">Relative * Layout</a> guide.</p> * @@ -202,18 +215,29 @@ public class RelativeLayout extends ViewGroup { private View[] mSortedVerticalChildren = new View[0]; private final DependencyGraph mGraph = new DependencyGraph(); + // Compatibility hack. Old versions of the platform had problems + // with MeasureSpec value overflow and RelativeLayout was one source of them. + // Some apps came to rely on them. :( + private boolean mAllowBrokenMeasureSpecs = false; + public RelativeLayout(Context context) { super(context); + mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <= + Build.VERSION_CODES.JELLY_BEAN_MR1; } public RelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); initFromAttributes(context, attrs); + mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <= + Build.VERSION_CODES.JELLY_BEAN_MR1; } public RelativeLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initFromAttributes(context, attrs); + mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <= + Build.VERSION_CODES.JELLY_BEAN_MR1; } private void initFromAttributes(Context context, AttributeSet attrs) { @@ -673,7 +697,12 @@ public class RelativeLayout extends ViewGroup { mPaddingLeft, mPaddingRight, myWidth); int childHeightMeasureSpec; - if (params.width == LayoutParams.MATCH_PARENT) { + if (myHeight < 0 && !mAllowBrokenMeasureSpecs) { + // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement + // is code for, "we got an unspecified mode in the RelativeLayout's measurespec." + // Carry it forward. + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } else if (params.width == LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST); @@ -700,6 +729,13 @@ public class RelativeLayout extends ViewGroup { private int getChildMeasureSpec(int childStart, int childEnd, int childSize, int startMargin, int endMargin, int startPadding, int endPadding, int mySize) { + if (mySize < 0 && !mAllowBrokenMeasureSpecs) { + // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement + // is code for, "we got an unspecified mode in the RelativeLayout's measurespec." + // Carry it forward. + return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + int childSpecMode = 0; int childSpecSize = 0; @@ -1655,7 +1691,7 @@ public class RelativeLayout extends ViewGroup { * * A node with no dependent is considered a root of the graph. */ - static class Node implements Poolable<Node> { + static class Node { /** * The view representing this node in the layout. */ @@ -1678,43 +1714,14 @@ public class RelativeLayout extends ViewGroup { // The pool is static, so all nodes instances are shared across // activities, that's why we give it a rather high limit private static final int POOL_LIMIT = 100; - private static final Pool<Node> sPool = Pools.synchronizedPool( - Pools.finitePool(new PoolableManager<Node>() { - public Node newInstance() { - return new Node(); - } - - public void onAcquired(Node element) { - } - - public void onReleased(Node element) { - } - }, POOL_LIMIT) - ); - - private Node mNext; - private boolean mIsPooled; - - public void setNextPoolable(Node element) { - mNext = element; - } - - public Node getNextPoolable() { - return mNext; - } - - public boolean isPooled() { - return mIsPooled; - } - - public void setPooled(boolean isPooled) { - mIsPooled = isPooled; - } + private static final SimplePool<Node> sPool = new SimplePool<Node>(POOL_LIMIT); static Node acquire(View view) { - final Node node = sPool.acquire(); + Node node = sPool.acquire(); + if (node == null) { + node = new Node(); + } node.view = view; - return node; } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index cd8638d..0281602 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -1247,6 +1247,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { */ @Override public void onActionViewCollapsed() { + setQuery("", false); clearFocus(); updateViewsVisibility(true); mQueryTextView.setImeOptions(mCollapsedImeOptions); diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index 2737f94..a6486a8 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -18,6 +18,7 @@ package android.widget; import android.content.Context; import android.util.AttributeSet; +import android.util.ValueModel; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -33,7 +34,7 @@ import android.view.accessibility.AccessibilityNodeInfo; * * @attr ref android.R.styleable#SeekBar_thumb */ -public class SeekBar extends AbsSeekBar { +public class SeekBar extends AbsSeekBar implements ValueEditor<Integer> { /** * A callback that notifies clients when the progress level has been @@ -69,8 +70,9 @@ public class SeekBar extends AbsSeekBar { void onStopTrackingTouch(SeekBar seekBar); } + private ValueModel<Integer> mValueModel = ValueModel.EMPTY; private OnSeekBarChangeListener mOnSeekBarChangeListener; - + public SeekBar(Context context) { this(context, null); } @@ -89,9 +91,23 @@ public class SeekBar extends AbsSeekBar { if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser); + if (fromUser) { + mValueModel.set(getProgress()); + } } } + @Override + public ValueModel<Integer> getValueModel() { + return mValueModel; + } + + @Override + public void setValueModel(ValueModel<Integer> valueModel) { + mValueModel = valueModel; + setProgress(mValueModel.get()); + } + /** * Sets a listener to receive notifications of changes to the SeekBar's progress level. Also * provides notifications of when the user starts and stops a touch gesture within the SeekBar. diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index 74ea038..9e7f97e 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -109,7 +109,7 @@ public class SpellChecker implements SpellCheckerSessionListener { mIds = new int[size]; mSpellCheckSpans = new SpellCheckSpan[size]; - setLocale(mTextView.getTextServicesLocale()); + setLocale(mTextView.getSpellCheckerLocale()); mCookie = hashCode(); } @@ -120,7 +120,8 @@ public class SpellChecker implements SpellCheckerSessionListener { mTextServicesManager = (TextServicesManager) mTextView.getContext(). getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); if (!mTextServicesManager.isSpellCheckerEnabled() - || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) { + || mCurrentLocale == null + || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) { mSpellCheckerSession = null; } else { mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession( @@ -146,8 +147,10 @@ public class SpellChecker implements SpellCheckerSessionListener { resetSession(); - // Change SpellParsers' wordIterator locale - mWordIterator = new WordIterator(locale); + if (locale != null) { + // Change SpellParsers' wordIterator locale + mWordIterator = new WordIterator(locale); + } // This class is the listener for locale change: warn other locale-aware objects mTextView.onLocaleChanged(); @@ -222,9 +225,9 @@ public class SpellChecker implements SpellCheckerSessionListener { if (DBG) { Log.d(TAG, "Start spell-checking: " + start + ", " + end); } - final Locale locale = mTextView.getTextServicesLocale(); + final Locale locale = mTextView.getSpellCheckerLocale(); final boolean isSessionActive = isSessionActive(); - if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) { + if (locale == null || mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) { setLocale(locale); // Re-check the entire text start = 0; diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 925864c..fa64fd3 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -25,6 +25,8 @@ import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -697,6 +699,69 @@ public class Spinner extends AbsSpinner implements OnClickListener { return width; } + @Override + public Parcelable onSaveInstanceState() { + final SavedState ss = new SavedState(super.onSaveInstanceState()); + ss.showDropdown = mPopup != null && mPopup.isShowing(); + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + + super.onRestoreInstanceState(ss.getSuperState()); + + if (ss.showDropdown) { + ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (!mPopup.isShowing()) { + mPopup.show(); + } + final ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + vto.removeOnGlobalLayoutListener(this); + } + } + }; + vto.addOnGlobalLayoutListener(listener); + } + } + } + + static class SavedState extends AbsSpinner.SavedState { + boolean showDropdown; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + showDropdown = in.readByte() != 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeByte((byte) (showDropdown ? 1 : 0)); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + /** * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance * into a ListAdapter.</p> @@ -941,8 +1006,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { mHintText = hintText; } - @Override - public void show() { + void computeContentWidth() { final Drawable background = getBackground(); int hOffset = 0; if (background != null) { @@ -955,6 +1019,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { final int spinnerPaddingLeft = Spinner.this.getPaddingLeft(); final int spinnerPaddingRight = Spinner.this.getPaddingRight(); final int spinnerWidth = Spinner.this.getWidth(); + if (mDropDownWidth == WRAP_CONTENT) { int contentWidth = measureContentWidth( (SpinnerAdapter) mAdapter, getBackground()); @@ -977,11 +1042,25 @@ public class Spinner extends AbsSpinner implements OnClickListener { hOffset += spinnerPaddingLeft; } setHorizontalOffset(hOffset); + } + + @Override + public void show() { + final boolean wasShowing = isShowing(); + + computeContentWidth(); + setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); super.show(); getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); setSelection(Spinner.this.getSelectedItemPosition()); + if (wasShowing) { + // Skip setting up the layout/dismiss listener below. If we were previously + // showing it will still stick around. + return; + } + // Make sure we hide if our anchor goes away. // TODO: This might be appropriate to push all the way down to PopupWindow, // but it may have other side effects to investigate first. (Text editing handles, etc.) @@ -992,6 +1071,12 @@ public class Spinner extends AbsSpinner implements OnClickListener { public void onGlobalLayout() { if (!Spinner.this.isVisibleToUser()) { dismiss(); + } else { + computeContentWidth(); + + // Use super.show here to update; we don't want to move the selected + // position or adjust other things that would be reset otherwise. + DropdownPopup.super.show(); } } }; diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java index 399b4fa..f4b2ce0 100644 --- a/core/java/android/widget/TableLayout.java +++ b/core/java/android/widget/TableLayout.java @@ -445,7 +445,7 @@ public class TableLayout extends LinearLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // enforce vertical layout - layoutVertical(); + layoutVertical(l, t, r, b); } /** diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index 35927e0..fe3631a 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -120,7 +120,7 @@ public class TableRow extends LinearLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // enforce horizontal layout - layoutHorizontal(); + layoutHorizontal(l, t, r, b); } /** diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 22bfadb..1f64c5b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Canvas; +import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -510,7 +511,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private InputFilter[] mFilters = NO_FILTERS; - private volatile Locale mCurrentTextServicesLocaleCache; + private volatile Locale mCurrentSpellCheckerLocaleCache; private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock(); // It is possible to have a selection even when mEditor is null (programmatically set, like when @@ -4349,6 +4350,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ///////////////////////////////////////////////////////////////////////// + private int getBoxHeight(Layout l) { + Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; + int padding = (l == mHintLayout) ? + getCompoundPaddingTop() + getCompoundPaddingBottom() : + getExtendedPaddingTop() + getExtendedPaddingBottom(); + return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; + } + int getVerticalOffset(boolean forceNormal) { int voffset = 0; final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; @@ -4359,15 +4368,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (gravity != Gravity.TOP) { - int boxht; - - if (l == mHintLayout) { - boxht = getMeasuredHeight() - getCompoundPaddingTop() - - getCompoundPaddingBottom(); - } else { - boxht = getMeasuredHeight() - getExtendedPaddingTop() - - getExtendedPaddingBottom(); - } + int boxht = getBoxHeight(l); int textht = l.getHeight(); if (textht < boxht) { @@ -4390,15 +4391,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (gravity != Gravity.BOTTOM) { - int boxht; - - if (l == mHintLayout) { - boxht = getMeasuredHeight() - getCompoundPaddingTop() - - getCompoundPaddingBottom(); - } else { - boxht = getMeasuredHeight() - getExtendedPaddingTop() - - getExtendedPaddingBottom(); - } + int boxht = getBoxHeight(l); int textht = l.getHeight(); if (textht < boxht) { @@ -5144,6 +5137,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener voffset = getVerticalOffset(true); } + if (isLayoutModeOptical(mParent)) { + voffset -= getOpticalInsets().top; + } + return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); } @@ -7828,27 +7825,46 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener (isTextSelectable() && mText instanceof Spannable && isEnabled()); } + private Locale getTextServicesLocale(boolean allowNullLocale) { + // Start fetching the text services locale asynchronously. + updateTextServicesLocaleAsync(); + // If !allowNullLocale and there is no cached text services locale, just return the default + // locale. + return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() + : mCurrentSpellCheckerLocaleCache; + } + /** * This is a temporary method. Future versions may support multi-locale text. * Caveat: This method may not return the latest text services locale, but this should be * acceptable and it's more important to make this method asynchronous. * - * @return The locale that should be used for a word iterator and a spell checker + * @return The locale that should be used for a word iterator * in this TextView, based on the current spell checker settings, * the current IME's locale, or the system default locale. + * Please note that a word iterator in this TextView is different from another word iterator + * used by SpellChecker.java of TextView. This method should be used for the former. * @hide */ // TODO: Support multi-locale // TODO: Update the text services locale immediately after the keyboard locale is switched // by catching intent of keyboard switch event public Locale getTextServicesLocale() { - if (mCurrentTextServicesLocaleCache == null) { - // If there is no cached text services locale, just return the default locale. - mCurrentTextServicesLocaleCache = Locale.getDefault(); - } - // Start fetching the text services locale asynchronously. - updateTextServicesLocaleAsync(); - return mCurrentTextServicesLocaleCache; + return getTextServicesLocale(false /* allowNullLocale */); + } + + /** + * This is a temporary method. Future versions may support multi-locale text. + * Caveat: This method may not return the latest spell checker locale, but this should be + * acceptable and it's more important to make this method asynchronous. + * + * @return The locale that should be used for a spell checker in this TextView, + * based on the current spell checker settings, the current IME's locale, or the system default + * locale. + * @hide + */ + public Locale getSpellCheckerLocale() { + return getTextServicesLocale(true /* allowNullLocale */); } private void updateTextServicesLocaleAsync() { @@ -7867,14 +7883,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void updateTextServicesLocaleLocked() { - Locale locale = Locale.getDefault(); final TextServicesManager textServicesManager = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); + final Locale locale; if (subtype != null) { locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale()); + } else { + locale = null; } - mCurrentTextServicesLocaleCache = locale; + mCurrentSpellCheckerLocaleCache = locale; } void onLocaleChanged() { diff --git a/core/java/android/widget/ValueEditor.java b/core/java/android/widget/ValueEditor.java new file mode 100755 index 0000000..2b91abf --- /dev/null +++ b/core/java/android/widget/ValueEditor.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 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 android.util.ValueModel; + +/** + * An interface for editors of simple values. Classes implementing this interface are normally + * UI controls (subclasses of {@link android.view.View View}) that can provide a suitable + * user interface to display and edit values of the specified type. This interface is + * intended to describe editors for simple types, like {@code boolean}, {@code int} or + * {@code String}, where the values themselves are immutable. + * <p> + * For example, {@link android.widget.CheckBox CheckBox} implements + * this interface for the Boolean type as it is capable of providing an appropriate + * mechanism for displaying and changing the value of a Boolean property. + * + * @param <T> the value type that this editor supports + */ +public interface ValueEditor<T> { + /** + * Return the last value model that was set. If no value model has been set, the editor + * should return the value {@link android.util.ValueModel#EMPTY}. + * + * @return the value model + */ + public ValueModel<T> getValueModel(); + + /** + * Sets the value model for this editor. When the value model is set, the editor should + * retrieve the value from the value model, using {@link android.util.ValueModel#get()}, + * and set its internal state accordingly. Likewise, when the editor's internal state changes + * it should update the value model by calling {@link android.util.ValueModel#set(T)} + * with the appropriate value. + * + * @param valueModel the new value model for this editor. + */ + public void setValueModel(ValueModel<T> valueModel); +} diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 329b0df..16b6a76 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -35,6 +35,7 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; +import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.MediaController.MediaPlayerControl; @@ -107,23 +108,65 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - //Log.i("@@@@", "onMeasure"); + //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " + // + MeasureSpec.toString(heightMeasureSpec) + ")"); + int width = getDefaultSize(mVideoWidth, widthMeasureSpec); int height = getDefaultSize(mVideoHeight, heightMeasureSpec); if (mVideoWidth > 0 && mVideoHeight > 0) { - if ( mVideoWidth * height > width * mVideoHeight ) { - //Log.i("@@@", "image too tall, correcting"); + + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { + // the size is fixed + width = widthSpecSize; + height = heightSpecSize; + + // for compatibility, we adjust size based on aspect ratio + if ( mVideoWidth * height < width * mVideoHeight ) { + //Log.i("@@@", "image too wide, correcting"); + width = height * mVideoWidth / mVideoHeight; + } else if ( mVideoWidth * height > width * mVideoHeight ) { + //Log.i("@@@", "image too tall, correcting"); + height = width * mVideoHeight / mVideoWidth; + } + } else if (widthSpecMode == MeasureSpec.EXACTLY) { + // only the width is fixed, adjust the height to match aspect ratio if possible + width = widthSpecSize; height = width * mVideoHeight / mVideoWidth; - } else if ( mVideoWidth * height < width * mVideoHeight ) { - //Log.i("@@@", "image too wide, correcting"); + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + // couldn't match aspect ratio within the constraints + height = heightSpecSize; + } + } else if (heightSpecMode == MeasureSpec.EXACTLY) { + // only the height is fixed, adjust the width to match aspect ratio if possible + height = heightSpecSize; width = height * mVideoWidth / mVideoHeight; + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + // couldn't match aspect ratio within the constraints + width = widthSpecSize; + } } else { - //Log.i("@@@", "aspect ratio is correct: " + - //width+"/"+height+"="+ - //mVideoWidth+"/"+mVideoHeight); + // neither the width nor the height are fixed, try to use actual video size + width = mVideoWidth; + height = mVideoHeight; + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + // too tall, decrease both width and height + height = heightSpecSize; + width = height * mVideoWidth / mVideoHeight; + } + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + // too wide, decrease both width and height + width = widthSpecSize; + height = width * mVideoHeight / mVideoWidth; + } } + } else { + // no size yet, just adopt the given spec sizes } - //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height); setMeasuredDimension(width, height); } @@ -140,33 +183,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { } public int resolveAdjustedSize(int desiredSize, int measureSpec) { - int result = desiredSize; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - - switch (specMode) { - case MeasureSpec.UNSPECIFIED: - /* Parent says we can be as big as we want. Just don't be larger - * than max size imposed on ourselves. - */ - result = desiredSize; - break; - - case MeasureSpec.AT_MOST: - /* Parent says we can be as big as we want, up to specSize. - * Don't be larger than specSize, and don't be larger than - * the max size imposed on ourselves. - */ - result = Math.min(desiredSize, specSize); - break; - - case MeasureSpec.EXACTLY: - // No choice. Do what we are told. - result = specSize; - break; - } - return result; -} + return getDefaultSize(desiredSize, measureSpec); + } private void initVideoView() { mVideoWidth = 0; |
