summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountManagerService.java30
-rw-r--r--core/java/android/app/ApplicationPackageManager.java17
-rw-r--r--core/java/android/content/Intent.java45
-rw-r--r--core/java/android/content/SyncManager.java23
-rw-r--r--core/java/android/content/SyncQueue.java13
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageManager.java18
-rw-r--r--core/java/android/content/pm/PackageParser.java97
-rw-r--r--core/java/android/view/DisplayList.java24
-rw-r--r--core/java/android/view/FocusFinder.java82
-rw-r--r--core/java/android/view/GLES20Canvas.java8
-rw-r--r--core/java/android/view/HardwareCanvas.java16
-rw-r--r--core/java/android/view/HardwareRenderer.java10
-rw-r--r--core/java/android/view/View.java43
-rw-r--r--core/java/android/view/ViewGroup.java15
-rw-r--r--core/java/android/webkit/DebugFlags.java7
-rw-r--r--core/java/android/webkit/WebViewClassic.java23
-rw-r--r--core/java/android/webkit/WebViewCore.java1
-rw-r--r--core/java/android/widget/TextView.java6
-rw-r--r--core/java/com/android/internal/util/StateMachine.java61
20 files changed, 391 insertions, 150 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 27c9c8b..197c1bd 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -231,6 +231,14 @@ public class AccountManagerService
}
}, intentFilter);
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onUserRemoved(intent);
+ }
+ }, userFilter);
}
private UserAccounts initUser(int userId) {
@@ -347,6 +355,28 @@ public class AccountManagerService
}
}
+ private void onUserRemoved(Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+ if (userId < 1) return;
+
+ UserAccounts accounts;
+ synchronized (mUsers) {
+ accounts = mUsers.get(userId);
+ mUsers.remove(userId);
+ }
+ if (accounts == null) {
+ File dbFile = new File(getDatabaseName(userId));
+ dbFile.delete();
+ return;
+ }
+
+ synchronized (accounts.cacheLock) {
+ accounts.openHelper.close();
+ File dbFile = new File(getDatabaseName(userId));
+ dbFile.delete();
+ }
+ }
+
private List<UserInfo> getAllUsers() {
try {
return AppGlobals.getPackageManager().getUsers();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f38540c..0510de1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1215,6 +1215,18 @@ final class ApplicationPackageManager extends PackageManager {
* @hide
*/
@Override
+ public UserInfo getUser(int userId) {
+ try {
+ return mPM.getUser(userId);
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
public boolean removeUser(int id) {
try {
return mPM.removeUser(id);
@@ -1228,7 +1240,10 @@ final class ApplicationPackageManager extends PackageManager {
*/
@Override
public void updateUserName(int id, String name) {
- // TODO:
+ try {
+ mPM.updateUserName(id, name);
+ } catch (RemoteException re) {
+ }
}
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1c9ef38..2a9f1af 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2141,6 +2141,30 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_PRE_BOOT_COMPLETED =
"android.intent.action.PRE_BOOT_COMPLETED";
+ /**
+ * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USERID that has the
+ * userid of the new user.
+ * @hide
+ */
+ public static final String ACTION_USER_ADDED =
+ "android.intent.action.USER_ADDED";
+
+ /**
+ * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USERID that has
+ * the userid of the user.
+ * @hide
+ */
+ public static final String ACTION_USER_REMOVED =
+ "android.intent.action.USER_REMOVED";
+
+ /**
+ * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USERID that has
+ * the userid of the user to become the current one.
+ * @hide
+ */
+ public static final String ACTION_USER_SWITCHED =
+ "android.intent.action.USER_SWITCHED";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2682,6 +2706,13 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_LOCAL_ONLY =
"android.intent.extra.LOCAL_ONLY";
+ /**
+ * The userid carried with broadcast intents related to addition, removal and switching of users
+ * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+ * @hide
+ */
+ public static final String EXTRA_USERID =
+ "android.intent.extra.user_id";
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
@@ -6483,7 +6514,12 @@ public class Intent implements Parcelable, Cloneable {
final String action = getAction();
if (ACTION_SEND.equals(action)) {
- final Uri stream = getParcelableExtra(EXTRA_STREAM);
+ final Uri stream;
+ try {
+ stream = getParcelableExtra(EXTRA_STREAM);
+ } catch (ClassCastException e) {
+ return;
+ }
if (stream != null) {
final ClipData clipData = new ClipData(
null, new String[] { getType() }, new ClipData.Item(stream));
@@ -6493,7 +6529,12 @@ public class Intent implements Parcelable, Cloneable {
}
} else if (ACTION_SEND_MULTIPLE.equals(action)) {
- final ArrayList<Uri> streams = getParcelableArrayListExtra(EXTRA_STREAM);
+ final ArrayList<Uri> streams;
+ try {
+ streams = getParcelableArrayListExtra(EXTRA_STREAM);
+ } catch (ClassCastException e) {
+ return;
+ }
if (streams != null && streams.size() > 0) {
final Uri firstStream = streams.get(0);
final ClipData clipData = new ClipData(
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index b7dfe92..06dfe90 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -326,6 +326,13 @@ public class SyncManager implements OnAccountsUpdateListener {
}
};
+ private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onUserRemoved(intent);
+ }
+ };
+
private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
private final SyncHandler mSyncHandler;
@@ -420,6 +427,10 @@ public class SyncManager implements OnAccountsUpdateListener {
intentFilter.setPriority(100);
context.registerReceiver(mShutdownIntentReceiver, intentFilter);
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(mUserIntentReceiver, intentFilter);
+
if (!factoryTest) {
mNotificationMgr = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -905,6 +916,18 @@ public class SyncManager implements OnAccountsUpdateListener {
}
}
+ private void onUserRemoved(Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+ if (userId == -1) return;
+
+ // Clean up the storage engine database
+ mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
+ onAccountsUpdated(null);
+ synchronized (mSyncQueue) {
+ mSyncQueue.removeUser(userId);
+ }
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index 06da6fa..c18c86b 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -117,6 +117,19 @@ public class SyncQueue {
return true;
}
+ public void removeUser(int userId) {
+ ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
+ for (SyncOperation op : mOperationsMap.values()) {
+ if (op.userId == userId) {
+ opsToRemove.add(op);
+ }
+ }
+
+ for (SyncOperation op : opsToRemove) {
+ remove(op);
+ }
+ }
+
/**
* Remove the specified operation if it is in the queue.
* @param operation the operation to remove
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index d89d2de..56fd5f8 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -358,6 +358,7 @@ interface IPackageManager {
UserInfo createUser(in String name, int flags);
boolean removeUser(int userId);
+ void updateUserName(int userId, String name);
void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer,
int flags, in String installerPackageName, in Uri verificationURI,
@@ -370,6 +371,7 @@ interface IPackageManager {
boolean isFirstBoot();
List<UserInfo> getUsers();
+ UserInfo getUser(int userId);
void setPermissionEnforcement(String permission, int enforcement);
int getPermissionEnforcement(String permission);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 55426b8..b06b4a5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2153,7 +2153,8 @@ public abstract class PackageManager {
if ((flags & GET_SIGNATURES) != 0) {
packageParser.collectCertificates(pkg, 0);
}
- return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null);
+ return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, false,
+ COMPONENT_ENABLED_STATE_DEFAULT);
}
/**
@@ -2637,10 +2638,17 @@ public abstract class PackageManager {
public abstract void updateUserFlags(int id, int flags);
/**
- * Returns the device identity that verifiers can use to associate their
- * scheme to a particular device. This should not be used by anything other
- * than a package verifier.
- *
+ * Returns the details for the user specified by userId.
+ * @param userId the user id of the user
+ * @return UserInfo for the specified user, or null if no such user exists.
+ * @hide
+ */
+ public abstract UserInfo getUser(int userId);
+
+ /**
+ * Returns the device identity that verifiers can use to associate their scheme to a particular
+ * device. This should not be used by anything other than a package verifier.
+ *
* @return identity that uniquely identifies current device
* @hide
*/
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 07d231a..eb8536f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -230,6 +230,15 @@ public class PackageParser {
return name.endsWith(".apk");
}
+ public static PackageInfo generatePackageInfo(PackageParser.Package p,
+ int gids[], int flags, long firstInstallTime, long lastUpdateTime,
+ HashSet<String> grantedPermissions) {
+
+ return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
+ grantedPermissions, false, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ UserId.getCallingUserId());
+ }
+
/**
* Generate and return the {@link PackageInfo} for a parsed package.
*
@@ -238,15 +247,15 @@ public class PackageParser {
*/
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
- HashSet<String> grantedPermissions) {
+ HashSet<String> grantedPermissions, boolean stopped, int enabledState) {
return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
- grantedPermissions, UserId.getCallingUserId());
+ grantedPermissions, stopped, enabledState, UserId.getCallingUserId());
}
- static PackageInfo generatePackageInfo(PackageParser.Package p,
+ public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
- HashSet<String> grantedPermissions, int userId) {
+ HashSet<String> grantedPermissions, boolean stopped, int enabledState, int userId) {
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
@@ -254,7 +263,7 @@ public class PackageParser {
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
- pi.applicationInfo = generateApplicationInfo(p, flags);
+ pi.applicationInfo = generateApplicationInfo(p, flags, stopped, enabledState, userId);
pi.installLocation = p.installLocation;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
@@ -290,7 +299,7 @@ public class PackageParser {
if (activity.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags,
- userId);
+ stopped, enabledState, userId);
}
}
}
@@ -311,7 +320,8 @@ public class PackageParser {
final Activity activity = p.receivers.get(i);
if (activity.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags, userId);
+ pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags,
+ stopped, enabledState, userId);
}
}
}
@@ -332,7 +342,8 @@ public class PackageParser {
final Service service = p.services.get(i);
if (service.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.services[j++] = generateServiceInfo(p.services.get(i), flags, userId);
+ pi.services[j++] = generateServiceInfo(p.services.get(i), flags, stopped,
+ enabledState, userId);
}
}
}
@@ -353,7 +364,8 @@ public class PackageParser {
final Provider provider = p.providers.get(i);
if (provider.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, userId);
+ pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, stopped,
+ enabledState, userId);
}
}
}
@@ -3068,11 +3080,11 @@ public class PackageParser {
// For use by package manager to keep track of where it has done dexopt.
public boolean mDidDexOpt;
- // User set enabled state.
- public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
-
- // Whether the package has been stopped.
- public boolean mSetStopped = false;
+ // // User set enabled state.
+ // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+ //
+ // // Whether the package has been stopped.
+ // public boolean mSetStopped = false;
// Additional data supplied by callers.
public Object mExtras;
@@ -3337,9 +3349,9 @@ public class PackageParser {
}
}
- private static boolean copyNeeded(int flags, Package p, Bundle metaData) {
- if (p.mSetEnabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
- boolean enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ private static boolean copyNeeded(int flags, Package p, int enabledState, Bundle metaData) {
+ if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+ boolean enabled = enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
if (p.applicationInfo.enabled != enabled) {
return true;
}
@@ -3355,30 +3367,32 @@ public class PackageParser {
return false;
}
- public static ApplicationInfo generateApplicationInfo(Package p, int flags) {
- return generateApplicationInfo(p, flags, UserId.getCallingUserId());
+ public static ApplicationInfo generateApplicationInfo(Package p, int flags, boolean stopped,
+ int enabledState) {
+ return generateApplicationInfo(p, flags, stopped, enabledState, UserId.getCallingUserId());
}
- public static ApplicationInfo generateApplicationInfo(Package p, int flags, int userId) {
+ public static ApplicationInfo generateApplicationInfo(Package p, int flags,
+ boolean stopped, int enabledState, int userId) {
if (p == null) return null;
- if (!copyNeeded(flags, p, null) && userId == 0) {
+ if (!copyNeeded(flags, p, enabledState, null) && userId == 0) {
// CompatibilityMode is global state. It's safe to modify the instance
// of the package.
if (!sCompatibilityModeEnabled) {
p.applicationInfo.disableCompatibilityMode();
}
- if (p.mSetStopped) {
+ if (stopped) {
p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
} else {
p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED;
}
- if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
p.applicationInfo.enabled = true;
- } else if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
p.applicationInfo.enabled = false;
}
- p.applicationInfo.enabledSetting = p.mSetEnabled;
+ p.applicationInfo.enabledSetting = enabledState;
return p.applicationInfo;
}
@@ -3397,18 +3411,18 @@ public class PackageParser {
if (!sCompatibilityModeEnabled) {
ai.disableCompatibilityMode();
}
- if (p.mSetStopped) {
+ if (stopped) {
p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
} else {
p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED;
}
- if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
ai.enabled = true;
- } else if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
ai.enabled = false;
}
- ai.enabledSetting = p.mSetEnabled;
+ ai.enabledSetting = enabledState;
return ai;
}
@@ -3455,15 +3469,16 @@ public class PackageParser {
}
}
- public static final ActivityInfo generateActivityInfo(Activity a, int flags, int userId) {
+ public static final ActivityInfo generateActivityInfo(Activity a, int flags, boolean stopped,
+ int enabledState, int userId) {
if (a == null) return null;
- if (!copyNeeded(flags, a.owner, a.metaData) && userId == 0) {
+ if (!copyNeeded(flags, a.owner, enabledState, a.metaData) && userId == 0) {
return a.info;
}
// Make shallow copies so we can store the metadata safely
ActivityInfo ai = new ActivityInfo(a.info);
ai.metaData = a.metaData;
- ai.applicationInfo = generateApplicationInfo(a.owner, flags, userId);
+ ai.applicationInfo = generateApplicationInfo(a.owner, flags, stopped, enabledState, userId);
return ai;
}
@@ -3488,16 +3503,17 @@ public class PackageParser {
}
}
- public static final ServiceInfo generateServiceInfo(Service s, int flags, int userId) {
+ public static final ServiceInfo generateServiceInfo(Service s, int flags, boolean stopped,
+ int enabledState, int userId) {
if (s == null) return null;
- if (!copyNeeded(flags, s.owner, s.metaData)
+ if (!copyNeeded(flags, s.owner, enabledState, s.metaData)
&& userId == UserId.getUserId(s.info.applicationInfo.uid)) {
return s.info;
}
// Make shallow copies so we can store the metadata safely
ServiceInfo si = new ServiceInfo(s.info);
si.metaData = s.metaData;
- si.applicationInfo = generateApplicationInfo(s.owner, flags, userId);
+ si.applicationInfo = generateApplicationInfo(s.owner, flags, stopped, enabledState, userId);
return si;
}
@@ -3530,9 +3546,10 @@ public class PackageParser {
}
}
- public static final ProviderInfo generateProviderInfo(Provider p, int flags, int userId) {
+ public static final ProviderInfo generateProviderInfo(Provider p, int flags, boolean stopped,
+ int enabledState, int userId) {
if (p == null) return null;
- if (!copyNeeded(flags, p.owner, p.metaData)
+ if (!copyNeeded(flags, p.owner, enabledState, p.metaData)
&& ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
|| p.info.uriPermissionPatterns == null)
&& userId == 0) {
@@ -3544,7 +3561,7 @@ public class PackageParser {
if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
pi.uriPermissionPatterns = null;
}
- pi.applicationInfo = generateApplicationInfo(p.owner, flags, userId);
+ pi.applicationInfo = generateApplicationInfo(p.owner, flags, stopped, enabledState, userId);
return pi;
}
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index a50f09f..e2aafa9 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -35,6 +35,30 @@ public abstract class DisplayList {
*/
public static final int FLAG_CLIP_CHILDREN = 0x1;
+ // NOTE: The STATUS_* values *must* match the enum in DrawGlInfo.h
+
+ /**
+ * Indicates that the display list is done drawing.
+ *
+ * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int)
+ */
+ public static final int STATUS_DONE = 0x0;
+
+ /**
+ * Indicates that the display list needs another drawing pass.
+ *
+ * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int)
+ */
+ public static final int STATUS_DRAW = 0x1;
+
+ /**
+ * Indicates that the display list needs to re-execute its GL functors.
+ *
+ * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int)
+ * @see HardwareCanvas#callDrawGLFunction(int)
+ */
+ public static final int STATUS_INVOKE = 0x2;
+
/**
* Starts recording the display list. All operations performed on the
* returned canvas are recorded and stored in this display list.
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index d9bf918..9639faf 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -79,25 +79,45 @@ public class FocusFinder {
switch (direction) {
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
+ setFocusBottomRight(root);
+ break;
case View.FOCUS_FORWARD:
- final int rootTop = root.getScrollY();
- final int rootLeft = root.getScrollX();
- mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+ if (focused != null && focused.isLayoutRtl()) {
+ setFocusTopLeft(root);
+ } else {
+ setFocusBottomRight(root);
+ }
break;
case View.FOCUS_LEFT:
case View.FOCUS_UP:
+ setFocusTopLeft(root);
+ break;
case View.FOCUS_BACKWARD:
- final int rootBottom = root.getScrollY() + root.getHeight();
- final int rootRight = root.getScrollX() + root.getWidth();
- mFocusedRect.set(rootRight, rootBottom,
- rootRight, rootBottom);
+ if (focused != null && focused.isLayoutRtl()) {
+ setFocusBottomRight(root);
+ } else {
+ setFocusTopLeft(root);
break;
+ }
}
}
return findNextFocus(root, focused, mFocusedRect, direction);
}
+ private void setFocusTopLeft(ViewGroup root) {
+ final int rootBottom = root.getScrollY() + root.getHeight();
+ final int rootRight = root.getScrollX() + root.getWidth();
+ mFocusedRect.set(rootRight, rootBottom,
+ rootRight, rootBottom);
+ }
+
+ private void setFocusBottomRight(ViewGroup root) {
+ final int rootTop = root.getScrollY();
+ final int rootLeft = root.getScrollX();
+ mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+ }
+
/**
* Find the next view to take focus in root's descendants, searching from
* a particular rectangle in root's coordinates.
@@ -135,22 +155,10 @@ public class FocusFinder {
final int count = focusables.size();
switch (direction) {
case View.FOCUS_FORWARD:
- if (focused != null) {
- int position = focusables.lastIndexOf(focused);
- if (position >= 0 && position + 1 < count) {
- return focusables.get(position + 1);
- }
- }
- return focusables.get(0);
+ return getForwardFocusable(focused, focusables, count);
case View.FOCUS_BACKWARD:
- if (focused != null) {
- int position = focusables.indexOf(focused);
- if (position > 0) {
- return focusables.get(position - 1);
- }
- }
- return focusables.get(count - 1);
+ return getBackwardFocusable(focused, focusables, count);
}
return null;
}
@@ -193,6 +201,38 @@ public class FocusFinder {
return closest;
}
+ private View getForwardFocusable(View focused, ArrayList<View> focusables, int count) {
+ return (focused != null && focused.isLayoutRtl()) ?
+ getPreviousFocusable(focused, focusables, count) :
+ getNextFocusable(focused, focusables, count);
+ }
+
+ private View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+ if (focused != null) {
+ int position = focusables.lastIndexOf(focused);
+ if (position >= 0 && position + 1 < count) {
+ return focusables.get(position + 1);
+ }
+ }
+ return focusables.get(0);
+ }
+
+ private View getBackwardFocusable(View focused, ArrayList<View> focusables, int count) {
+ return (focused != null && focused.isLayoutRtl()) ?
+ getNextFocusable(focused, focusables, count) :
+ getPreviousFocusable(focused, focusables, count);
+ }
+
+ private View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+ if (focused != null) {
+ int position = focusables.indexOf(focused);
+ if (position > 0) {
+ return focusables.get(position - 1);
+ }
+ }
+ return focusables.get(count - 1);
+ }
+
/**
* Is rect1 a better candidate than rect2 for a focus search in a particular
* direction from a source rect? This is the core routine that determines
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 1f75e70..0e96742 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -296,11 +296,11 @@ class GLES20Canvas extends HardwareCanvas {
///////////////////////////////////////////////////////////////////////////
@Override
- public boolean callDrawGLFunction(int drawGLFunction) {
+ public int callDrawGLFunction(int drawGLFunction) {
return nCallDrawGLFunction(mRenderer, drawGLFunction);
}
- private static native boolean nCallDrawGLFunction(int renderer, int drawGLFunction);
+ private static native int nCallDrawGLFunction(int renderer, int drawGLFunction);
///////////////////////////////////////////////////////////////////////////
// Memory
@@ -394,13 +394,13 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nSetDisplayListName(int displayList, String name);
@Override
- public boolean drawDisplayList(DisplayList displayList, int width, int height,
+ public int drawDisplayList(DisplayList displayList, int width, int height,
Rect dirty, int flags) {
return nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList(),
width, height, dirty, flags);
}
- private static native boolean nDrawDisplayList(int renderer, int displayList,
+ private static native int nDrawDisplayList(int renderer, int displayList,
int width, int height, Rect dirty, int flags);
@Override
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 838c03c..2636ea2 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -59,11 +59,11 @@ public abstract class HardwareCanvas extends Canvas {
* if this method returns true, can be null.
* @param flags Optional flags about drawing, see {@link DisplayList} for
* the possible flags.
- *
- * @return True if the content of the display list requires another
- * drawing pass (invalidate()), false otherwise
+ *
+ * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
+ * {@link DisplayList#STATUS_INVOKE}
*/
- public abstract boolean drawDisplayList(DisplayList displayList, int width, int height,
+ public abstract int drawDisplayList(DisplayList displayList, int width, int height,
Rect dirty, int flags);
/**
@@ -90,10 +90,12 @@ public abstract class HardwareCanvas extends Canvas {
* This function may return true if an invalidation is needed after the call.
*
* @param drawGLFunction A native function pointer
- * @return true if an invalidate is needed after the call, false otherwise
+ *
+ * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
+ * {@link DisplayList#STATUS_INVOKE}
*/
- public boolean callDrawGLFunction(int drawGLFunction) {
+ public int callDrawGLFunction(int drawGLFunction) {
// Noop - this is done in the display list recorder subclass
- return false;
+ return DisplayList.STATUS_DONE;
}
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index f98cfc0..d40043f 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -966,7 +966,6 @@ public abstract class HardwareRenderer {
Log.d("DLProperties", "getDisplayList():\t" +
mProfileData[mProfileCurrentFrame]);
}
-
}
if (displayList != null) {
@@ -975,7 +974,7 @@ public abstract class HardwareRenderer {
drawDisplayListStartTime = System.nanoTime();
}
- boolean invalidateNeeded = canvas.drawDisplayList(displayList,
+ int status = canvas.drawDisplayList(displayList,
view.getWidth(), view.getHeight(), mRedrawClip,
DisplayList.FLAG_CLIP_CHILDREN);
@@ -986,19 +985,18 @@ public abstract class HardwareRenderer {
if (ViewDebug.DEBUG_LATENCY) {
Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- drawDisplayList() took " +
- total + "ms, invalidateNeeded=" +
- invalidateNeeded + ".");
+ total + "ms, status=" + status);
}
}
- if (invalidateNeeded) {
+ if (status != DisplayList.STATUS_DONE) {
if (mRedrawClip.isEmpty()) {
attachInfo.mViewRootImpl.invalidate();
} else {
attachInfo.mViewRootImpl.invalidateChildInParent(
null, mRedrawClip);
+ mRedrawClip.setEmpty();
}
- mRedrawClip.setEmpty();
}
} else {
// Shouldn't reach here
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f769e96..2deeba6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1459,7 +1459,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* apps.
* @hide
*/
- public static final boolean USE_DISPLAY_LIST_PROPERTIES = false;
+ public static final boolean USE_DISPLAY_LIST_PROPERTIES = true;
/**
* Map used to store views' tags.
@@ -7401,7 +7401,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
invalidateViewProperty(false, false);
if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
- mDisplayList.setCameraDistance(distance);
+ mDisplayList.setCameraDistance(-Math.abs(distance) / dpi);
}
}
@@ -10747,16 +10747,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int layerType = (
!(mParent instanceof ViewGroup) || ((ViewGroup)mParent).mDrawLayers) ?
getLayerType() : LAYER_TYPE_NONE;
- if (!isLayer && layerType == LAYER_TYPE_HARDWARE && USE_DISPLAY_LIST_PROPERTIES) {
- final HardwareLayer layer = getHardwareLayer();
- if (layer != null && layer.isValid()) {
- canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
+ if (!isLayer && layerType != LAYER_TYPE_NONE && USE_DISPLAY_LIST_PROPERTIES) {
+ if (layerType == LAYER_TYPE_HARDWARE) {
+ final HardwareLayer layer = getHardwareLayer();
+ if (layer != null && layer.isValid()) {
+ canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
+ } else {
+ canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
+ Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ }
+ caching = true;
} else {
- canvas.saveLayer(0, 0,
- mRight - mLeft, mBottom - mTop, mLayerPaint,
- Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ buildDrawingCache(true);
+ Bitmap cache = getDrawingCache(true);
+ if (cache != null) {
+ canvas.drawBitmap(cache, 0, 0, mLayerPaint);
+ caching = true;
+ }
}
- caching = true;
} else {
computeScroll();
@@ -11395,7 +11404,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mTransformationInfo.mRotation, mTransformationInfo.mRotationX,
mTransformationInfo.mRotationY, mTransformationInfo.mScaleX,
mTransformationInfo.mScaleY);
- displayList.setCameraDistance(getCameraDistance());
+ if (mTransformationInfo.mCamera == null) {
+ mTransformationInfo.mCamera = new Camera();
+ mTransformationInfo.matrix3D = new Matrix();
+ }
+ displayList.setCameraDistance(mTransformationInfo.mCamera.getLocationZ());
if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == PIVOT_EXPLICITLY_SET) {
displayList.setPivotX(getPivotX());
displayList.setPivotY(getPivotY());
@@ -11489,8 +11502,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
} else {
switch (layerType) {
case LAYER_TYPE_SOFTWARE:
- buildDrawingCache(true);
- cache = getDrawingCache(true);
+ if (useDisplayListProperties) {
+ hasDisplayList = canHaveDisplayList();
+ } else {
+ buildDrawingCache(true);
+ cache = getDrawingCache(true);
+ }
break;
case LAYER_TYPE_HARDWARE:
if (useDisplayListProperties) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6ccac78..30d6ec7 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3906,9 +3906,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
do {
if (parent instanceof ViewGroup) {
ViewGroup parentVG = (ViewGroup) parent;
- parent = parentVG.invalidateChildInParentFast(left, top, dirty);
- left = parentVG.mLeft;
- top = parentVG.mTop;
+ if (parentVG.mLayerType != LAYER_TYPE_NONE) {
+ // Layered parents should be recreated, not just re-issued
+ parentVG.invalidate();
+ parent = null;
+ } else {
+ parent = parentVG.invalidateChildInParentFast(left, top, dirty);
+ left = parentVG.mLeft;
+ top = parentVG.mTop;
+ }
} else {
// Reached the top; this calls into the usual invalidate method in
// ViewRootImpl, which schedules a traversal
@@ -4664,6 +4670,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public void clearDisappearingChildren() {
if (mDisappearingChildren != null) {
mDisappearingChildren.clear();
+ invalidate();
}
}
@@ -4775,7 +4782,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
view.mParent = null;
}
}
- mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+ invalidate();
}
}
}
diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java
index a21d3ee..349113e 100644
--- a/core/java/android/webkit/DebugFlags.java
+++ b/core/java/android/webkit/DebugFlags.java
@@ -42,12 +42,7 @@ class DebugFlags {
public static final boolean WEB_BACK_FORWARD_LIST = false;
public static final boolean WEB_SETTINGS = false;
public static final boolean WEB_SYNC_MANAGER = false;
- public static final boolean WEB_TEXT_VIEW = false;
public static final boolean WEB_VIEW = false;
public static final boolean WEB_VIEW_CORE = false;
- /*
- * Set to true to allow the WebTextView to draw on top of the web page in a
- * different color with no background so you can see how the two line up.
- */
- public static final boolean DRAW_WEBTEXTVIEW = false;
+ public static final boolean MEASURE_PAGE_SWAP_FPS = false;
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index ab2db22..5ae2fe0 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -4717,10 +4717,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
queueFull = nativeSetBaseLayer(mNativeClass, layer, invalRegion,
showVisualIndicator, isPictureAfterFirstLayout);
- if (layer == 0 || isPictureAfterFirstLayout) {
- mWebViewCore.resumeWebKitDraw();
- } else if (queueFull) {
+ if (queueFull) {
mWebViewCore.pauseWebKitDraw();
+ } else {
+ mWebViewCore.resumeWebKitDraw();
}
if (mHTML5VideoViewProxy != null) {
@@ -8615,9 +8615,19 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
void onPageSwapOccurred(boolean notifyAnimationStarted);
}
+ long mLastSwapTime;
+ double mAverageSwapFps;
+
/** Called by JNI when pages are swapped (only occurs with hardware
* acceleration) */
protected void pageSwapCallback(boolean notifyAnimationStarted) {
+ if (DebugFlags.MEASURE_PAGE_SWAP_FPS) {
+ long now = System.currentTimeMillis();
+ long diff = now - mLastSwapTime;
+ mAverageSwapFps = ((1000.0 / diff) + mAverageSwapFps) / 2;
+ Log.d(LOGTAG, "page swap fps: " + mAverageSwapFps);
+ mLastSwapTime = now;
+ }
mWebViewCore.resumeWebKitDraw();
if (notifyAnimationStarted) {
mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
@@ -8679,7 +8689,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
b.left+","+b.top+","+b.right+","+b.bottom+"}");
}
- invalidateContentRect(draw.mInvalRegion.getBounds());
+ Rect invalBounds = draw.mInvalRegion.getBounds();
+ if (!invalBounds.isEmpty()) {
+ invalidateContentRect(invalBounds);
+ } else {
+ mWebView.invalidate();
+ }
if (mPictureListener != null) {
mPictureListener.onNewPicture(getWebView(), capturePicture());
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index b47f71d..afb2992 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2271,6 +2271,7 @@ public final class WebViewCore {
mFirstLayoutForNonStandardLoad = false;
}
if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
+ pauseWebKitDraw();
Message.obtain(mWebViewClassic.mPrivateHandler,
WebViewClassic.NEW_PICTURE_MSG_ID, draw).sendToTarget();
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 16d1b94..1f2410b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7496,7 +7496,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Returns true, only while processing a touch gesture, if the initial
* touch down event caused focus to move to the text view and as a result
* its selection changed. Only valid while processing the touch gesture
- * of interest.
+ * of interest, in an editable text view.
*/
public boolean didTouchFocusSelect() {
return mEditor != null && getEditor().mTouchFocusSelected;
@@ -11683,7 +11683,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
highlight = null;
}
- if (false /* TEMP patch for bugs 6198276 & 6193544 */ && canHaveDisplayList() && canvas.isHardwareAccelerated()) {
+ if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlight, cursorOffsetVertical);
} else {
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
@@ -11758,7 +11758,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
hardwareCanvas.onPostDraw();
blockDisplayList.end();
if (USE_DISPLAY_LIST_PROPERTIES) {
- blockDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+ blockDisplayList.setLeftTopRightBottom(0, 0, width, height);
}
}
}
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index da189f1..07496a7 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -44,10 +44,14 @@ import java.util.Vector;
* <p>When a state machine is created <code>addState</code> is used to build the
* hierarchy and <code>setInitialState</code> is used to identify which of these
* is the initial state. After construction the programmer calls <code>start</code>
- * which initializes the state machine and calls <code>enter</code> for all of the initial
- * state's hierarchy, starting at its eldest parent. For example given the simple
- * state machine below after start is called mP1.enter will have been called and
- * then mS1.enter.</p>
+ * which initializes and starts the state machine. The first action the StateMachine
+ * is to the invoke <code>enter</code> for all of the initial state's hierarchy,
+ * starting at its eldest parent. The calls to enter will be done in the context
+ * of the StateMachines Handler not in the context of the call to start and they
+ * will be invoked before any messages are processed. For example, given the simple
+ * state machine below mP1.enter will be invoked and then mS1.enter. Finally,
+ * messages sent to the state machine will be processed by the current state,
+ * in our simple state machine below that would initially be mS1.processMessage.</p>
<code>
mP1
/ \
@@ -621,8 +625,8 @@ public class StateMachine {
/** The debug flag */
private boolean mDbg = false;
- /** The quit object */
- private static final Object mQuitObj = new Object();
+ /** The SmHandler object, identifies that message is internal */
+ private static final Object mSmHandlerObj = new Object();
/** The current message */
private Message mMsg;
@@ -726,19 +730,18 @@ public class StateMachine {
/** Save the current message */
mMsg = msg;
- /**
- * Check that construction was completed
- */
- if (!mIsConstructionCompleted) {
- Log.e(TAG, "The start method not called, ignore msg: " + msg);
- return;
+ if (mIsConstructionCompleted) {
+ /** Normal path */
+ processMsg(msg);
+ } else if (!mIsConstructionCompleted &&
+ (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
+ /** Initial one time path. */
+ mIsConstructionCompleted = true;
+ invokeEnterMethods(0);
+ } else {
+ throw new RuntimeException("StateMachine.handleMessage: " +
+ "The start method not called, received msg: " + msg);
}
-
- /**
- * Process the message abiding by the hierarchical semantics
- * and perform any requested transitions.
- */
- processMsg(msg);
performTransitions();
if (mDbg) Log.d(TAG, "handleMessage: X");
@@ -852,18 +855,8 @@ public class StateMachine {
mTempStateStack = new StateInfo[maxDepth];
setupInitialStateStack();
- /**
- * Construction is complete call all enter methods
- * starting at the first entry.
- */
- mIsConstructionCompleted = true;
- mMsg = obtainMessage(SM_INIT_CMD);
- invokeEnterMethods(0);
-
- /**
- * Perform any transitions requested by the enter methods
- */
- performTransitions();
+ /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
+ sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
if (mDbg) Log.d(TAG, "completeConstruction: X");
}
@@ -1103,14 +1096,14 @@ public class StateMachine {
/** @see StateMachine#setInitialState(State) */
private final void setInitialState(State initialState) {
- if (mDbg) Log.d(TAG, "setInitialState: initialState" + initialState.getName());
+ if (mDbg) Log.d(TAG, "setInitialState: initialState=" + initialState.getName());
mInitialState = initialState;
}
/** @see StateMachine#transitionTo(IState) */
private final void transitionTo(IState destState) {
mDestState = (State) destState;
- if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + mDestState.getName());
+ if (mDbg) Log.d(TAG, "transitionTo: destState=" + mDestState.getName());
}
/** @see StateMachine#deferMessage(Message) */
@@ -1127,12 +1120,12 @@ public class StateMachine {
/** @see StateMachine#deferMessage(Message) */
private final void quit() {
if (mDbg) Log.d(TAG, "quit:");
- sendMessage(obtainMessage(SM_QUIT_CMD, mQuitObj));
+ sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
}
/** @see StateMachine#isQuit(Message) */
private final boolean isQuit(Message msg) {
- return (msg.what == SM_QUIT_CMD) && (msg.obj == mQuitObj);
+ return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
}
/** @see StateMachine#isDbg() */