diff options
Diffstat (limited to 'core')
122 files changed, 2944 insertions, 6832 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 54e0375..fc72f9a 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -155,7 +155,7 @@ public class AccountManagerService new AtomicReference<AccountManagerService>(); private static final boolean isDebuggableMonkeyBuild = - SystemProperties.getBoolean("ro.monkey", false) + SystemProperties.getBoolean("monkey.running", false) && SystemProperties.getBoolean("ro.debuggable", false); private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 676d6d5..930ab65 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -606,7 +606,7 @@ public class ActivityManager { public int uid; /** - * The tag that was provided when the process crashed. + * The activity name associated with the error, if known. May be null. */ public String tag; @@ -891,6 +891,38 @@ public class ActivityManager { } /** + * @deprecated This is now just a wrapper for + * {@link #killBackgroundProcesses(String)}; the previous behavior here + * is no longer available to applications because it allows them to + * break other applications by removing their alarms, stopping their + * services, etc. + */ + @Deprecated + public void restartPackage(String packageName) { + killBackgroundProcesses(packageName); + } + + /** + * Have the system immediately kill all background processes associated + * with the given package. This is the same as the kernel killing those + * processes to reclaim memory; the system will take care of restarting + * these processes in the future as needed. + * + * <p>You must hold the permission + * {@link android.Manifest.permission#KILL_BACKGROUND_PROCESSES} to be able to + * call this method. + * + * @param packageName The name of the package whose processes are to + * be killed. + */ + public void killBackgroundProcesses(String packageName) { + try { + ActivityManagerNative.getDefault().killBackgroundProcesses(packageName); + } catch (RemoteException e) { + } + } + + /** * Have the system perform a force stop of everything associated with * the given application package. All processes that share its uid * will be killed, all services it has running stopped, all activities @@ -899,14 +931,18 @@ public class ActivityManager { * be stopped, notifications removed, etc. * * <p>You must hold the permission - * {@link android.Manifest.permission#RESTART_PACKAGES} to be able to + * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to * call this method. * * @param packageName The name of the package to be stopped. + * + * @hide This is not available to third party applications due to + * it allowing them to break other applications by stopping their + * services, removing their alarms, etc. */ - public void restartPackage(String packageName) { + public void forceStopPackage(String packageName) { try { - ActivityManagerNative.getDefault().restartPackage(packageName); + ActivityManagerNative.getDefault().forceStopPackage(packageName); } catch (RemoteException e) { } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index a0498aa..09b88ee 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -979,13 +979,23 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case HANDLE_APPLICATION_ERROR_TRANSACTION: { + case HANDLE_APPLICATION_CRASH_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder app = data.readStrongBinder(); + ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data); + handleApplicationCrash(app, ci); + reply.writeNoException(); + return true; + } + + case HANDLE_APPLICATION_WTF_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder app = data.readStrongBinder(); String tag = data.readString(); ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data); - handleApplicationError(app, tag, ci); + boolean res = handleApplicationWtf(app, tag, ci); reply.writeNoException(); + reply.writeInt(res ? 1 : 0); return true; } @@ -997,10 +1007,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case RESTART_PACKAGE_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); + case KILL_BACKGROUND_PROCESSES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); String packageName = data.readString(); - restartPackage(packageName); + killBackgroundProcesses(packageName); + reply.writeNoException(); + return true; + } + + case FORCE_STOP_PACKAGE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String packageName = data.readString(); + forceStopPackage(packageName); reply.writeNoException(); return true; } @@ -2337,7 +2355,20 @@ class ActivityManagerProxy implements IActivityManager /* this base class version is never called */ return true; } - public void handleApplicationError(IBinder app, String tag, + public void handleApplicationCrash(IBinder app, + ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(app); + crashInfo.writeToParcel(data, 0); + mRemote.transact(HANDLE_APPLICATION_CRASH_TRANSACTION, data, reply, 0); + reply.readException(); + reply.recycle(); + data.recycle(); + } + public boolean handleApplicationWtf(IBinder app, String tag, ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException { Parcel data = Parcel.obtain(); @@ -2346,10 +2377,12 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(app); data.writeString(tag); crashInfo.writeToParcel(data, 0); - mRemote.transact(HANDLE_APPLICATION_ERROR_TRANSACTION, data, reply, 0); + mRemote.transact(HANDLE_APPLICATION_WTF_TRANSACTION, data, reply, 0); reply.readException(); + boolean res = reply.readInt() != 0; reply.recycle(); data.recycle(); + return res; } public void signalPersistentProcesses(int sig) throws RemoteException { @@ -2363,12 +2396,23 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public void restartPackage(String packageName) throws RemoteException { + public void killBackgroundProcesses(String packageName) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + mRemote.transact(KILL_BACKGROUND_PROCESSES_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void forceStopPackage(String packageName) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(packageName); - mRemote.transact(RESTART_PACKAGE_TRANSACTION, data, reply, 0); + mRemote.transact(FORCE_STOP_PACKAGE_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); diff --git a/core/java/android/app/AliasActivity.java b/core/java/android/app/AliasActivity.java index 4f91e02..7527a5b 100644 --- a/core/java/android/app/AliasActivity.java +++ b/core/java/android/app/AliasActivity.java @@ -26,7 +26,7 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import android.util.AttributeSet; import android.util.Xml; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import java.io.IOException; diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index b3d16e9..d89b877 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -17,7 +17,7 @@ package android.app; import com.android.internal.policy.PolicyManager; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import com.google.android.collect.Maps; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index e89b3ad0..a4b692f 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -195,6 +195,7 @@ public class ApplicationErrorReport implements Parcelable { StringWriter sw = new StringWriter(); tr.printStackTrace(new PrintWriter(sw)); stackTrace = sw.toString(); + exceptionMessage = tr.getMessage(); // Populate fields with the "root cause" exception while (tr.getCause() != null) { diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl index 804dd61..c76a517 100644 --- a/core/java/android/app/IActivityController.aidl +++ b/core/java/android/app/IActivityController.aidl @@ -44,7 +44,7 @@ interface IActivityController * it immediately. */ boolean appCrashed(String processName, int pid, - String tag, String shortMsg, String longMsg, + String shortMsg, String longMsg, long timeMillis, String stackTrace); /** diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index c890c4c..016d465 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -216,7 +216,8 @@ public interface IActivityManager extends IInterface { public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException; - public void restartPackage(final String packageName) throws RemoteException; + public void killBackgroundProcesses(final String packageName) throws RemoteException; + public void forceStopPackage(final String packageName) throws RemoteException; // Note: probably don't want to allow applications access to these. public void goingToSleep() throws RemoteException; @@ -242,8 +243,9 @@ public interface IActivityManager extends IInterface { // Special low-level communication with activity manager. public void startRunning(String pkg, String cls, String action, String data) throws RemoteException; - - public void handleApplicationError(IBinder app, String tag, + public void handleApplicationCrash(IBinder app, + ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; + public boolean handleApplicationWtf(IBinder app, String tag, ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; /* @@ -349,7 +351,7 @@ public interface IActivityManager extends IInterface { // Please keep these transaction codes the same -- they are also // sent by C++ code. int START_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; - int HANDLE_APPLICATION_ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1; + int HANDLE_APPLICATION_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1; int START_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2; int UNHANDLED_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3; int OPEN_CONTENT_URI_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4; @@ -423,7 +425,7 @@ public interface IActivityManager extends IInterface { int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+75; int GET_PROCESSES_IN_ERROR_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+76; int CLEAR_APP_DATA_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+77; - int RESTART_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78; + int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78; int KILL_PIDS_FOR_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79; int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80; int REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81; @@ -446,4 +448,6 @@ public interface IActivityManager extends IInterface { int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98; int START_ACTIVITY_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+99; int OVERRIDE_PENDING_TRANSITION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+100; + int HANDLE_APPLICATION_WTF_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+101; + int KILL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+102; } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index fda9b81..7e5f858 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -83,6 +83,8 @@ public final class BluetoothA2dp { /** Default priority for a2dp devices that should not allow incoming * connections */ public static final int PRIORITY_OFF = 0; + /** Default priority when not set or when the device is unpaired */ + public static final int PRIORITY_UNDEFINED = -1; private final IBluetoothA2dp mService; private final Context mContext; diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 5eb655a..b792965 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -109,6 +109,8 @@ public final class BluetoothHeadset { /** Default priority for headsets that should not be auto-connected * and not allow incoming connections. */ public static final int PRIORITY_OFF = 0; + /** Default priority when not set or when the device is unpaired */ + public static final int PRIORITY_UNDEFINED = -1; /** The voice dialer 'works' but the user experience is poor. The voice * recognizer has trouble dealing with the 8kHz SCO signal, and it still diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html index 4f0755e..5ff240c 100644 --- a/core/java/android/bluetooth/package.html +++ b/core/java/android/bluetooth/package.html @@ -12,96 +12,16 @@ devices, connecting with devices, and managing data transfer between devices. <li>Transfer data to and from other devices</li> </ul> -<p class="note"><strong>Note:</strong> +<p> To perform Bluetooth communication using these APIs, an application must declare the {@link android.Manifest.permission#BLUETOOTH} permission. Some -additional functionality, such as requesting device discovery and -pairing also requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} +additional functionality, such as requesting device discovery, +also requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. </p> -<h3>Overview</h3> - -<p>Here's a basic introduction to the Bluetooth classes:</p> -<dl> - <dt>{@link android.bluetooth.BluetoothAdapter}</dt> - <dd>This represents the local Bluetooth adapter, which is essentially the - entry-point to performing any interaction with Bluetooth. With it, you can - discover other Bluetooth devices, query a list of bonded (paired) devices, - initialize a {@link android.bluetooth.BluetoothDevice} using a known MAC - address, and create a {@link android.bluetooth.BluetoothServerSocket} to - listen for communications from other devices.</dd> - - <dt>{@link android.bluetooth.BluetoothDevice}</dt> - <dd>This represents a remote Bluetooth device. Use this to request a - connection with a remote device through a - {@link android.bluetooth.BluetoothSocket} - or query information about the device such as its name, address, class, and - bonding state.</dd> - - <dt>{@link android.bluetooth.BluetoothSocket}</dt> - <dd>This represents the interface for a Bluetooth socket - (similar to a TCP client-side {@link java.net.Socket}). This is the - connection point that allows an app to transfer data with another Bluetooth - device via {@link java.io.InputStream} and {@link java.io.OutputStream}.</dd> - <dt>{@link android.bluetooth.BluetoothServerSocket}</dt> - - <dd>This represents an open server socket that listens for incoming requests - (similar to a TCP server-side {@link java.net.ServerSocket}). - When attempting to connect two Android devices, one device will need to open - a server socket with this class. When a connection is accepted, a new - {@link android.bluetooth.BluetoothSocket} will be returned, - which can be used to manage the connection and transfer data.</dd> - - <dt>{@link android.bluetooth.BluetoothClass}</dt> - <dd>This represents the Bluetooth class for a device which describes general - characteristics and capabilities of a device. This class and its subclasses - don't provide any actual functionality. The sub-classes are entirely composed - of constants for the device and service class definitions.</dd> -</dl> - - -<h3>Example Procedure</h3> - -<p>For example, here's an pseudo-code procedure for discovering and -connecting a remote device, and transfering data:</p> - -<ol> - <li>Register a {@link android.content.BroadcastReceiver} that accepts the - {@link android.bluetooth.BluetoothDevice#ACTION_FOUND} Intent.</li> - <li>Call {@link android.bluetooth.BluetoothAdapter#getDefaultAdapter} to - retrieve the Android system's local - {@link android.bluetooth.BluetoothAdapter}.</li> - <li>Call {@link android.bluetooth.BluetoothAdapter#startDiscovery() - BluetoothAdapter.startDiscovery()} to scan for local devices. This is where - the BroadcastReceiver comes in; Android now scans for devices and will - broadcast the {@link android.bluetooth.BluetoothDevice#ACTION_FOUND} Intent - for each remote device discovered. The - {@link android.content.BroadcastReceiver} - you created will receive each Intent.</li> - <li>The {@link android.bluetooth.BluetoothDevice#ACTION_FOUND} Intent - includes the {@link android.bluetooth.BluetoothDevice#EXTRA_DEVICE} - Parcelable extra, which is a {@link android.bluetooth.BluetoothDevice} - object. Extract this from the Intent and call - {@link android.bluetooth.BluetoothDevice#createRfcommSocketToServiceRecord(java.util.UUID) - BluetoothDevice.createRfcommSocketToServiceRecord()} - to open a {@link android.bluetooth.BluetoothSocket} with a chosen - remote device.</li> - <li>Call {@link android.bluetooth.BluetoothSocket#connect() - BluetoothSocket.connect()} to connect with the remote device.</li> - <li>When successfully connected, call - {@link android.bluetooth.BluetoothSocket#getInputStream() - BluetoothSocket.getInputStream()} and/or - {@link android.bluetooth.BluetoothSocket#getOutputStream() - BluetoothSocket.getOutputStream()} to retreive an - {@link java.io.InputStream} and {@link java.io.OutputStream}, respectively, - which are hooked into the socket.</li> - <li>Use {@link java.io.InputStream#read(byte[]) InputStream.read()} and - {@link java.io.OutputStream#write(byte[]) OutputStream.write()} to transfer - data.</li> -</ol> - - +<p>For a detailed guide to using the Bluetooth APIs, see the <a +href="{@docRoot}guide/topics/wireless/bluetooth.html">Bluetooth Dev Guide topic</a>.</p> <p class="note"><strong>Note:</strong> Not all Android devices are guaranteed to have Bluetooth functionality.</p> diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2ab5357..0fafe5d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1314,7 +1314,7 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@blink android.appwidget.AppWidgetManager} for accessing AppWidgets. + * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets. * * @hide * @see #getSystemService @@ -1323,7 +1323,7 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve an - * {@blink android.backup.IBackupManager IBackupManager} for communicating + * {@link android.backup.IBackupManager IBackupManager} for communicating * with the backup mechanism. * @hide * @@ -1333,7 +1333,7 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@blink android.os.DropBox DropBox} instance for recording + * {@link android.os.DropBoxManager} instance for recording * diagnostic logs. * @see #getSystemService */ diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index d784759..bf37b62 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -34,7 +34,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import java.io.IOException; import java.io.Serializable; @@ -575,7 +575,7 @@ import java.util.Set; * {@link #setFlags} and {@link #addFlags}. See {@link #setFlags} for a list * of all possible flags. */ -public class Intent implements Parcelable { +public class Intent implements Parcelable, Cloneable { // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent activity actions (see action variable). @@ -1130,7 +1130,7 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY"; - + /** * Activity Action: Setup wizard to launch after a platform update. This * activity should have a string meta-data field associated with it, @@ -1144,7 +1144,7 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; - + /** * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity * describing the last run version of the platform that was setup. @@ -1158,7 +1158,7 @@ public class Intent implements Parcelable { /** * Broadcast Action: Sent after the screen turns off. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1166,7 +1166,7 @@ public class Intent implements Parcelable { public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF"; /** * Broadcast Action: Sent after the screen turns on. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1176,7 +1176,7 @@ public class Intent implements Parcelable { /** * Broadcast Action: Sent when the user is present after device wakes up (e.g when the * keyguard is gone). - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1189,7 +1189,7 @@ public class Intent implements Parcelable { * in manifests, only by exlicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1210,7 +1210,7 @@ public class Intent implements Parcelable { * <ul> * <li><em>time-zone</em> - The java.util.TimeZone.getID() value identifying the new time zone.</li> * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1238,7 +1238,7 @@ public class Intent implements Parcelable { * such as installing alarms. You must hold the * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission * in order to receive this broadcast. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1254,7 +1254,7 @@ public class Intent implements Parcelable { * Broadcast Action: Trigger the download and eventual installation * of a package. * <p>Input: {@link #getData} is the URI of the package file to download. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1270,7 +1270,7 @@ public class Intent implements Parcelable { * <li> {@link #EXTRA_REPLACING} is set to true if this is following * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package. * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1284,7 +1284,7 @@ public class Intent implements Parcelable { * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package. * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1302,7 +1302,7 @@ public class Intent implements Parcelable { * <li> {@link #EXTRA_REPLACING} is set to true if this will be followed * by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package. * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1319,7 +1319,7 @@ public class Intent implements Parcelable { * <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the * default action of restarting the application. * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1335,7 +1335,7 @@ public class Intent implements Parcelable { * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1350,7 +1350,7 @@ public class Intent implements Parcelable { * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1359,7 +1359,7 @@ public class Intent implements Parcelable { /** * Broadcast Action: A user ID has been removed from the system. The user * ID number is stored in the extra data under {@link #EXTRA_UID}. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1380,13 +1380,13 @@ public class Intent implements Parcelable { * application to make sure it sees the new changes. Some system code that * can not be restarted will need to watch for this action and handle it * appropriately. - * + * * <p class="note"> * You can <em>not</em> receive this through components declared * in manifests, only by explicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. * @@ -1396,7 +1396,7 @@ public class Intent implements Parcelable { public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; /** * Broadcast Action: The current device's locale has changed. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1417,7 +1417,7 @@ public class Intent implements Parcelable { * and {@link #ACTION_POWER_DISCONNECTED} for distinct battery-related * broadcasts that are sent and can be received through manifest * receivers. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1426,7 +1426,7 @@ public class Intent implements Parcelable { /** * Broadcast Action: Indicates low battery condition on the device. * This broadcast corresponds to the "Low battery warning" system dialog. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1436,7 +1436,7 @@ public class Intent implements Parcelable { * Broadcast Action: Indicates the battery is now okay after being low. * This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has * gone back up to an okay state. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1448,7 +1448,7 @@ public class Intent implements Parcelable { * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to * stay active to receive this notification. This action can be used to implement actions * that wait until power is available to trigger. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1460,7 +1460,7 @@ public class Intent implements Parcelable { * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to * stay active to receive this notification. This action can be used to implement actions * that wait until power is available to trigger. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1473,7 +1473,7 @@ public class Intent implements Parcelable { * off, not sleeping). Once the broadcast is complete, the final shutdown * will proceed and all unsaved data lost. Apps will not normally need * to handle this, since the foreground activity will be paused as well. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1493,7 +1493,7 @@ public class Intent implements Parcelable { /** * Broadcast Action: A sticky broadcast that indicates low memory * condition on the device - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1501,7 +1501,7 @@ public class Intent implements Parcelable { public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW"; /** * Broadcast Action: Indicates low memory condition on the device no longer exists - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1668,7 +1668,7 @@ public class Intent implements Parcelable { * then cell radio and possibly other radios such as bluetooth or WiFi may have also been * turned off</li> * </ul> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1750,7 +1750,7 @@ public class Intent implements Parcelable { * <p>You must hold the * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS} * permission to receive this Intent.</p> - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -1761,7 +1761,7 @@ public class Intent implements Parcelable { /** * Broadcast Action: Have the device reboot. This is only for use by * system code. - * + * * <p class="note">This is a protected intent that can only be sent * by the system. */ @@ -2112,7 +2112,7 @@ public class Intent implements Parcelable { * indicate that the dock should take over the home key when it is active. */ public static final String METADATA_DOCK_HOME = "android.dock_home"; - + /** * Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing * the bug report. @@ -2406,7 +2406,7 @@ public class Intent implements Parcelable { * the new broadcast (and receivers associated with it) will replace the * existing one in the pending broadcast list, remaining at the same * position in the list. - * + * * <p>This flag is most typically used with sticky broadcasts, which * only care about delivering the most recent values of the broadcast * to their receivers. @@ -2440,7 +2440,7 @@ public class Intent implements Parcelable { public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION; - + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // toUri() and parseUri() options. @@ -2454,7 +2454,7 @@ public class Intent implements Parcelable { * VIEW action for that raw URI. */ public static final int URI_INTENT_SCHEME = 1<<0; - + // --------------------------------------------------------------------- private String mAction; @@ -2613,7 +2613,7 @@ public class Intent implements Parcelable { public static Intent getIntent(String uri) throws URISyntaxException { return parseUri(uri, 0); } - + /** * Create an intent from a URI. This URI may encode the action, * category, and other intent fields, if it was returned by @@ -2632,7 +2632,7 @@ public class Intent implements Parcelable { * @throws URISyntaxException Throws URISyntaxError if the basic URI syntax * it bad (as parsed by the Uri class) or the Intent data within the * URI is invalid. - * + * * @see #toUri */ public static Intent parseUri(String uri, int flags) throws URISyntaxException { @@ -2650,7 +2650,7 @@ public class Intent implements Parcelable { return intent; } } - + // simple case i = uri.lastIndexOf("#"); if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri)); @@ -2742,7 +2742,7 @@ public class Intent implements Parcelable { data = scheme + ':' + data; } } - + if (data.length() > 0) { try { intent.mData = Uri.parse(data); @@ -2751,7 +2751,7 @@ public class Intent implements Parcelable { } } } - + return intent; } catch (IndexOutOfBoundsException e) { @@ -2902,7 +2902,7 @@ public class Intent implements Parcelable { } else { intent.mData = Uri.parse(uri); } - + if (intent.mAction == null) { // By default, if no action is specified, then use VIEW. intent.mAction = ACTION_VIEW; @@ -5127,13 +5127,13 @@ public class Intent implements Parcelable { * used with {@link Uri#parse Uri.parse(String)}. The URI contains the * Intent's data as the base URI, with an additional fragment describing * the action, categories, type, flags, package, component, and extras. - * + * * <p>You can convert the returned string back to an Intent with * {@link #getIntent}. - * + * * @param flags Additional operating flags. Either 0 or * {@link #URI_INTENT_SCHEME}. - * + * * @return Returns a URI encoding URI string describing the entire contents * of the Intent. */ @@ -5157,13 +5157,13 @@ public class Intent implements Parcelable { data = data.substring(i+1); break; } - + // No scheme. break; } } uri.append(data); - + } else if ((flags&URI_INTENT_SCHEME) != 0) { uri.append("intent:"); } diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 365f269..023c024 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -34,7 +34,7 @@ import android.util.AndroidException; import android.util.Config; import android.util.Log; import android.util.Printer; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; /** * Structured description of Intent values to be matched. An IntentFilter can diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index b6bb7db..4c53201 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -16,6 +16,10 @@ package android.content; +import com.android.internal.os.AtomicFile; +import com.android.internal.util.ArrayUtils; +import com.android.common.FastXmlSerializer; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -37,10 +41,6 @@ import android.util.Log; import android.util.SparseArray; import android.util.Xml; -import com.android.internal.os.AtomicFile; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FastXmlSerializer; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -512,7 +512,7 @@ public class SyncStorageEngine extends Handler { SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); status.pending = true; - status.initialize = op.extras != null && + status.initialize = op.extras != null && op.extras.containsKey(ContentResolver.SYNC_EXTRAS_INITIALIZE) && op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 48d1add..ad99f54 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -35,7 +35,7 @@ import android.util.Config; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import java.io.File; import java.io.IOException; diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index b39a67d..b819fa0 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -43,7 +43,7 @@ import java.io.IOException; import java.io.FileInputStream; import com.android.internal.os.AtomicFile; -import com.android.internal.util.FastXmlSerializer; +import com.android.common.FastXmlSerializer; import com.google.android.collect.Maps; import com.google.android.collect.Lists; diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 453a83d..70baaef 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -44,7 +44,7 @@ import java.util.Arrays; * <selector xmlns:android="http://schemas.android.com/apk/res/android"> * <item android:state_focused="true" android:color="@color/testcolor1"/> * <item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /> - * <item android:state_enabled="false" android:colore="@color/testcolor3" /> + * <item android:state_enabled="false" android:color="@color/testcolor3" /> * <item android:state_active="true" android:color="@color/testcolor4" /> * <item android:color="@color/testcolor5"/> * </selector> diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 1c0ed36..e4fc259 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -17,7 +17,7 @@ package android.content.res; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java index 8fb82be..2411177 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -24,7 +24,7 @@ import android.util.SparseArray; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; /** * Conveniences for retrieving data out of a compiled string resource. diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 016ee7f..8f0003b 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -5,7 +5,7 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import java.util.Arrays; diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index 6336678..f800232 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -17,7 +17,7 @@ package android.content.res; import android.util.TypedValue; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/android/gesture/GestureStore.java b/core/java/android/gesture/GestureStore.java index 5f1a445..11a94d1 100644 --- a/core/java/android/gesture/GestureStore.java +++ b/core/java/android/gesture/GestureStore.java @@ -65,7 +65,12 @@ public class GestureStore { // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures public static final int ORIENTATION_INVARIANT = 1; + // at most 2 directions can be recognized public static final int ORIENTATION_SENSITIVE = 2; + // at most 4 directions can be recognized + static final int ORIENTATION_SENSITIVE_4 = 4; + // at most 8 directions can be recognized + static final int ORIENTATION_SENSITIVE_8 = 8; private static final short FILE_FORMAT_VERSION = 1; @@ -131,7 +136,7 @@ public class GestureStore { public ArrayList<Prediction> recognize(Gesture gesture) { Instance instance = Instance.createInstance(mSequenceType, mOrientationStyle, gesture, null); - return mClassifier.classify(mSequenceType, instance.vector); + return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector); } /** diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java index 40d7029..f1dcd89 100755 --- a/core/java/android/gesture/GestureUtilities.java +++ b/core/java/android/gesture/GestureUtilities.java @@ -366,6 +366,38 @@ final class GestureUtilities { } return Math.acos(sum); } + + /** + * Calculate the "minimum" cosine distance between two instances + * + * @param vector1 + * @param vector2 + * @param numOrientations the maximum number of orientation allowed + * @return the distance between the two instances (between 0 and Math.PI) + */ + static double minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) { + final int len = vector1.length; + double a = 0; + double b = 0; + for (int i = 0; i < len; i += 2) { + a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1]; + b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i]; + } + if (a != 0) { + final double tan = b/a; + final double angle = Math.atan(tan); + if (numOrientations > 2 && Math.abs(angle) >= Math.PI / numOrientations) { + return Math.acos(a); + } else { + final double cosine = Math.cos(angle); + final double sine = cosine * tan; + return Math.acos(a * cosine + b * sine); + } + } else { + return Math.PI / 2; + } + } + static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> pts) { GestureStroke stroke = new GestureStroke(pts); diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java index ef208ac..68a2985 100755 --- a/core/java/android/gesture/Instance.java +++ b/core/java/android/gesture/Instance.java @@ -94,7 +94,7 @@ class Instance { float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]); float adjustment = -orientation; - if (orientationType == GestureStore.ORIENTATION_SENSITIVE) { + if (orientationType != GestureStore.ORIENTATION_INVARIANT) { int count = ORIENTATIONS.length; for (int i = 0; i < count; i++) { float delta = ORIENTATIONS[i] - orientation; diff --git a/core/java/android/gesture/InstanceLearner.java b/core/java/android/gesture/InstanceLearner.java index b93b76f..9987e69 100644 --- a/core/java/android/gesture/InstanceLearner.java +++ b/core/java/android/gesture/InstanceLearner.java @@ -41,7 +41,7 @@ class InstanceLearner extends Learner { }; @Override - ArrayList<Prediction> classify(int sequenceType, float[] vector) { + ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector) { ArrayList<Prediction> predictions = new ArrayList<Prediction>(); ArrayList<Instance> instances = getInstances(); int count = instances.size(); @@ -53,7 +53,7 @@ class InstanceLearner extends Learner { } double distance; if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) { - distance = GestureUtilities.cosineDistance(sample.vector, vector); + distance = GestureUtilities.minimumCosineDistance(sample.vector, vector, orientationType); } else { distance = GestureUtilities.squaredEuclideanDistance(sample.vector, vector); } diff --git a/core/java/android/gesture/Learner.java b/core/java/android/gesture/Learner.java index feacde5..60997e0 100755 --- a/core/java/android/gesture/Learner.java +++ b/core/java/android/gesture/Learner.java @@ -79,5 +79,5 @@ abstract class Learner { instances.removeAll(toDelete); } - abstract ArrayList<Prediction> classify(int gestureType, float[] vector); + abstract ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector); } diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 76131fc..b0c3909 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -1118,6 +1118,11 @@ public class KeyboardView extends View implements View.OnClickListener { if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear(); mSwipeTracker.addMovement(me); + // Ignore all motion events until a DOWN. + if (mAbortKey && action != MotionEvent.ACTION_DOWN) { + return true; + } + if (mGestureDetector.onTouchEvent(me)) { showPreview(NOT_A_KEY); mHandler.removeMessages(MSG_REPEAT); @@ -1150,9 +1155,14 @@ public class KeyboardView extends View implements View.OnClickListener { mKeys[keyIndex].codes[0] : 0); if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) { mRepeatKeyIndex = mCurrentKey; - repeatKey(); Message msg = mHandler.obtainMessage(MSG_REPEAT); mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY); + repeatKey(); + // Delivering the key could have caused an abort + if (mAbortKey) { + mRepeatKeyIndex = NOT_A_KEY; + break; + } } if (mCurrentKey != NOT_A_KEY) { Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); diff --git a/core/java/android/net/NetworkConnectivityListener.java b/core/java/android/net/NetworkConnectivityListener.java deleted file mode 100644 index 858fc77..0000000 --- a/core/java/android/net/NetworkConnectivityListener.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2006 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.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Handler; -import android.os.Message; -import android.util.Log; - -import java.util.HashMap; -import java.util.Iterator; - -/** - * A wrapper for a broadcast receiver that provides network connectivity - * state information, independent of network type (mobile, Wi-Fi, etc.). - * {@hide} - */ -public class NetworkConnectivityListener { - private static final String TAG = "NetworkConnectivityListener"; - private static final boolean DBG = false; - - private Context mContext; - private HashMap<Handler, Integer> mHandlers = new HashMap<Handler, Integer>(); - private State mState; - private boolean mListening; - private String mReason; - private boolean mIsFailover; - - /** Network connectivity information */ - private NetworkInfo mNetworkInfo; - - /** - * In case of a Disconnect, the connectivity manager may have - * already established, or may be attempting to establish, connectivity - * with another network. If so, {@code mOtherNetworkInfo} will be non-null. - */ - private NetworkInfo mOtherNetworkInfo; - - private ConnectivityBroadcastReceiver mReceiver; - - private class ConnectivityBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) || - mListening == false) { - Log.w(TAG, "onReceived() called with " + mState.toString() + " and " + intent); - return; - } - - boolean noConnectivity = - intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - - if (noConnectivity) { - mState = State.NOT_CONNECTED; - } else { - mState = State.CONNECTED; - } - - mNetworkInfo = (NetworkInfo) - intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); - mOtherNetworkInfo = (NetworkInfo) - intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO); - - mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON); - mIsFailover = - intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false); - - if (DBG) { - Log.d(TAG, "onReceive(): mNetworkInfo=" + mNetworkInfo + " mOtherNetworkInfo = " - + (mOtherNetworkInfo == null ? "[none]" : mOtherNetworkInfo + - " noConn=" + noConnectivity) + " mState=" + mState.toString()); - } - - // Notifiy any handlers. - Iterator<Handler> it = mHandlers.keySet().iterator(); - while (it.hasNext()) { - Handler target = it.next(); - Message message = Message.obtain(target, mHandlers.get(target)); - target.sendMessage(message); - } - } - }; - - public enum State { - UNKNOWN, - - /** This state is returned if there is connectivity to any network **/ - CONNECTED, - /** - * This state is returned if there is no connectivity to any network. This is set - * to true under two circumstances: - * <ul> - * <li>When connectivity is lost to one network, and there is no other available - * network to attempt to switch to.</li> - * <li>When connectivity is lost to one network, and the attempt to switch to - * another network fails.</li> - */ - NOT_CONNECTED - } - - /** - * Create a new NetworkConnectivityListener. - */ - public NetworkConnectivityListener() { - mState = State.UNKNOWN; - mReceiver = new ConnectivityBroadcastReceiver(); - } - - /** - * This method starts listening for network connectivity state changes. - * @param context - */ - public synchronized void startListening(Context context) { - if (!mListening) { - mContext = context; - - IntentFilter filter = new IntentFilter(); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - context.registerReceiver(mReceiver, filter); - mListening = true; - } - } - - /** - * This method stops this class from listening for network changes. - */ - public synchronized void stopListening() { - if (mListening) { - mContext.unregisterReceiver(mReceiver); - mContext = null; - mNetworkInfo = null; - mOtherNetworkInfo = null; - mIsFailover = false; - mReason = null; - mListening = false; - } - } - - /** - * This methods registers a Handler to be called back onto with the specified what code when - * the network connectivity state changes. - * - * @param target The target handler. - * @param what The what code to be used when posting a message to the handler. - */ - public void registerHandler(Handler target, int what) { - mHandlers.put(target, what); - } - - /** - * This methods unregisters the specified Handler. - * @param target - */ - public void unregisterHandler(Handler target) { - mHandlers.remove(target); - } - - public State getState() { - return mState; - } - - /** - * Return the NetworkInfo associated with the most recent connectivity event. - * @return {@code NetworkInfo} for the network that had the most recent connectivity event. - */ - public NetworkInfo getNetworkInfo() { - return mNetworkInfo; - } - - /** - * If the most recent connectivity event was a DISCONNECT, return - * any information supplied in the broadcast about an alternate - * network that might be available. If this returns a non-null - * value, then another broadcast should follow shortly indicating - * whether connection to the other network succeeded. - * - * @return NetworkInfo - */ - public NetworkInfo getOtherNetworkInfo() { - return mOtherNetworkInfo; - } - - /** - * Returns true if the most recent event was for an attempt to switch over to - * a new network following loss of connectivity on another network. - * @return {@code true} if this was a failover attempt, {@code false} otherwise. - */ - public boolean isFailover() { - return mIsFailover; - } - - /** - * An optional reason for the connectivity state change may have been supplied. - * This returns it. - * @return the reason for the state change, if available, or {@code null} - * otherwise. - */ - public String getReason() { - return mReason; - } -} diff --git a/core/java/android/net/http/DomainNameChecker.java b/core/java/android/net/http/DomainNameChecker.java index e4c8009..3e01d2c 100644 --- a/core/java/android/net/http/DomainNameChecker.java +++ b/core/java/android/net/http/DomainNameChecker.java @@ -231,7 +231,7 @@ public class DomainNameChecker { rval = thisDomainTokens[i].equals(thatDomainTokens[i]); if (!rval) { // (c) OR we have a special *-match: - // Z.Y.X matches *.Y.X but does not match *.X + // *.Y.X matches Z.Y.X but *.X doesn't match Z.Y.X rval = (i == 0 && thisDomainTokensNum == thatDomainTokensNum); if (rval) { rval = thatDomainTokens[0].equals("*"); @@ -242,10 +242,13 @@ public class DomainNameChecker { thisDomainTokens[0], thatDomainTokens[0]); } } - break; } } + } else { + // (e) OR thatHost has a '*.'-prefix of thisHost: + // *.Y.X matches Y.X + rval = thatDomain.equals("*." + thisDomain); } } diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 7d2c698..d28148c 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -82,7 +82,7 @@ import java.util.concurrent.atomic.AtomicInteger; * <li><code>Result</code>, the type of the result of the background * computation.</li> * </ol> - * <p>Not all types are always used by am asynchronous task. To mark a type as unused, + * <p>Not all types are always used by an asynchronous task. To mark a type as unused, * simply use the type {@link Void}:</p> * <pre> * private class MyTask extends AsyncTask<Void, Void, Void> { ... } diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 6212b17..eed2af7 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -18,6 +18,8 @@ package android.os; import java.io.File; +import android.os.IMountService; + /** * Provides access to environment variables. */ @@ -28,6 +30,8 @@ public class Environment { private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; + private static IMountService mMntSvc = null; + /** * Gets the Android root directory. */ @@ -167,9 +171,19 @@ public class Environment { /** * Gets the current state of the external storage device. + * Note: This call should be deprecated as it doesn't support + * multiple volumes. */ public static String getExternalStorageState() { - return SystemProperties.get("EXTERNAL_STORAGE_STATE", MEDIA_REMOVED); + try { + if (mMntSvc == null) { + mMntSvc = IMountService.Stub.asInterface(ServiceManager + .getService("mount")); + } + return mMntSvc.getVolumeState(getExternalStorageDirectory().toString()); + } catch (android.os.RemoteException rex) { + return Environment.MEDIA_REMOVED; + } } static File getDirectory(String variableName, String defaultPath) { diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java index 38d252e..3457815 100644 --- a/core/java/android/os/FileObserver.java +++ b/core/java/android/os/FileObserver.java @@ -103,9 +103,7 @@ public abstract class FileObserver { try { observer.onEvent(mask, path); } catch (Throwable throwable) { - Log.e(LOG_TAG, "Unhandled throwable " + throwable.toString() + - " (returned by observer " + observer + ")", throwable); - RuntimeInit.crash("FileObserver", throwable); + Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable); } } } diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl index 96d44b6..1ea7200 100644 --- a/core/java/android/os/IMountService.aidl +++ b/core/java/android/os/IMountService.aidl @@ -75,4 +75,44 @@ interface IMountService * when a UMS host is detected. */ void setAutoStartUms(boolean value); + + /** + * Gets the state of an volume via it's mountpoint. + */ + String getVolumeState(String mountPoint); + + /* + * Creates a secure cache with the specified parameters. + * On success, the filesystem cache-path is returned. + */ + String createSecureCache(String id, int sizeMb, String fstype, String key, int ownerUid); + + /* + * Finalize a cache which has just been created and populated. + * After finalization, the cache is immutable. + */ + void finalizeSecureCache(String id); + + /* + * Destroy a secure cache, and free up all resources associated with it. + * NOTE: Ensure all references are released prior to deleting. + */ + void destroySecureCache(String id); + + /* + * Mount a secure cache with the specified key and owner UID. + * On success, the filesystem cache-path is returned. + */ + String mountSecureCache(String id, String key, int ownerUid); + + /* + * Returns the filesystem path of a mounted secure cache. + */ + String getSecureCachePath(String id); + + /** + * Gets an Array of currently known secure cache IDs + */ + String[] getSecureCacheList(); + } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 0afc537..23762ca 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -31,6 +31,7 @@ interface IPowerManager void preventScreenOn(boolean prevent); boolean isScreenOn(); void reboot(String reason); + void crash(String message); // sets the brightness of the backlights (screen, keyboard, button) 0-255 void setBacklightBrightness(int brightness); diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java index 03542dd..9742b05 100644 --- a/core/java/android/os/MemoryFile.java +++ b/core/java/android/os/MemoryFile.java @@ -52,7 +52,7 @@ public class MemoryFile private static native void native_write(FileDescriptor fd, int address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; - private static native int native_get_mapped_size(FileDescriptor fd) throws IOException; + private static native int native_get_size(FileDescriptor fd) throws IOException; private FileDescriptor mFD; // ashmem file descriptor private int mAddress; // address of ashmem memory @@ -300,20 +300,19 @@ public class MemoryFile * @hide */ public static boolean isMemoryFile(FileDescriptor fd) throws IOException { - return (native_get_mapped_size(fd) >= 0); + return (native_get_size(fd) >= 0); } /** - * Returns the size of the memory file, rounded up to a page boundary, that - * the file descriptor refers to, or -1 if the file descriptor does not - * refer to a memory file. + * Returns the size of the memory file that the file descriptor refers to, + * or -1 if the file descriptor does not refer to a memory file. * * @throws IOException If <code>fd</code> is not a valid file descriptor. * * @hide */ - public static int getMappedSize(FileDescriptor fd) throws IOException { - return native_get_mapped_size(fd); + public static int getSize(FileDescriptor fd) throws IOException { + return native_get_size(fd); } /** diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index caf0923..bc653d6 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -115,9 +115,7 @@ public class MessageQueue { didIdle = true; keep = ((IdleHandler)idler).queueIdle(); } catch (Throwable t) { - Log.e("MessageQueue", - "IdleHandler threw exception", t); - RuntimeInit.crash("MessageQueue", t); + Log.wtf("MessageQueue", "IdleHandler threw exception", t); } if (!keep) { diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 293dabc..e4eaf45 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -326,12 +326,11 @@ public class PowerManager { synchronized (mToken) { if (mHeld) { + Log.wtf(TAG, "WakeLock finalized while still held: " + mTag); try { mService.releaseWakeLock(mToken, 0); } catch (RemoteException e) { } - RuntimeInit.crash(TAG, new Exception( - "WakeLock finalized while still held: "+mTag)); } } } diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java new file mode 100644 index 0000000..3dd3918 --- /dev/null +++ b/core/java/android/os/RecoverySystem.java @@ -0,0 +1,418 @@ +/* + * 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.os; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.security.GeneralSecurityException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import android.content.Context; +import android.util.Log; + +import org.apache.harmony.security.asn1.BerInputStream; +import org.apache.harmony.security.pkcs7.ContentInfo; +import org.apache.harmony.security.pkcs7.SignedData; +import org.apache.harmony.security.pkcs7.SignerInfo; +import org.apache.harmony.security.provider.cert.X509CertImpl; + +/** + * RecoverySystem contains methods for interacting with the Android + * recovery system (the separate partition that can be used to install + * system updates, wipe user data, etc.) + */ +public class RecoverySystem { + private static final String TAG = "RecoverySystem"; + + /** + * Default location of zip file containing public keys (X509 + * certs) authorized to sign OTA updates. + */ + private static final File DEFAULT_KEYSTORE = + new File("/system/etc/security/otacerts.zip"); + + /** Send progress to listeners no more often than this (in ms). */ + private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500; + + /** Used to communicate with recovery. See bootable/recovery/recovery.c. */ + private static File RECOVERY_DIR = new File("/cache/recovery"); + private static File COMMAND_FILE = new File(RECOVERY_DIR, "command"); + private static File LOG_FILE = new File(RECOVERY_DIR, "log"); + + // Length limits for reading files. + private static int LOG_FILE_MAX_LENGTH = 8 * 1024; + + /** + * Interface definition for a callback to be invoked regularly as + * verification proceeds. + */ + public interface ProgressListener { + /** + * Called periodically as the verification progresses. + * + * @param progress the approximate percentage of the + * verification that has been completed, ranging from 0 + * to 100 (inclusive). + */ + public void onProgress(int progress); + } + + /** @return the set of certs that can be used to sign an OTA package. */ + private static HashSet<Certificate> getTrustedCerts(File keystore) + throws IOException, GeneralSecurityException { + HashSet<Certificate> trusted = new HashSet<Certificate>(); + if (keystore == null) { + keystore = DEFAULT_KEYSTORE; + } + ZipFile zip = new ZipFile(keystore); + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Enumeration<? extends ZipEntry> entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + trusted.add(cf.generateCertificate(zip.getInputStream(entry))); + } + } finally { + zip.close(); + } + return trusted; + } + + /** + * Verify the cryptographic signature of a system update package + * before installing it. Note that the package is also verified + * separately by the installer once the device is rebooted into + * the recovery system. This function will return only if the + * package was successfully verified; otherwise it will throw an + * exception. + * + * Verification of a package can take significant time, so this + * function should not be called from a UI thread. + * + * @param packageFile the package to be verified + * @param listener an object to receive periodic progress + * updates as verification proceeds. May be null. + * @param deviceCertsZipFile the zip file of certificates whose + * public keys we will accept. Verification succeeds if the + * package is signed by the private key corresponding to any + * public key in this file. May be null to use the system default + * file (currently "/system/etc/security/otacerts.zip"). + * + * @throws IOException if there were any errors reading the + * package or certs files. + * @throws GeneralSecurityException if verification failed + */ + public static void verifyPackage(File packageFile, + ProgressListener listener, + File deviceCertsZipFile) + throws IOException, GeneralSecurityException { + long fileLen = packageFile.length(); + + RandomAccessFile raf = new RandomAccessFile(packageFile, "r"); + try { + int lastPercent = 0; + long lastPublishTime = System.currentTimeMillis(); + if (listener != null) { + listener.onProgress(lastPercent); + } + + raf.seek(fileLen - 6); + byte[] footer = new byte[6]; + raf.readFully(footer); + + if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) { + throw new SignatureException("no signature in file (no footer)"); + } + + int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8); + int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8); + Log.v(TAG, String.format("comment size %d; signature start %d", + commentSize, signatureStart)); + + byte[] eocd = new byte[commentSize + 22]; + raf.seek(fileLen - (commentSize + 22)); + raf.readFully(eocd); + + // Check that we have found the start of the + // end-of-central-directory record. + if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b || + eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) { + throw new SignatureException("no signature in file (bad footer)"); + } + + for (int i = 4; i < eocd.length-3; ++i) { + if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b && + eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) { + throw new SignatureException("EOCD marker found after start of EOCD"); + } + } + + // The following code is largely copied from + // JarUtils.verifySignature(). We could just *call* that + // method here if that function didn't read the entire + // input (ie, the whole OTA package) into memory just to + // compute its message digest. + + BerInputStream bis = new BerInputStream( + new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart)); + ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis); + SignedData signedData = info.getSignedData(); + if (signedData == null) { + throw new IOException("signedData is null"); + } + Collection encCerts = signedData.getCertificates(); + if (encCerts.isEmpty()) { + throw new IOException("encCerts is empty"); + } + // Take the first certificate from the signature (packages + // should contain only one). + Iterator it = encCerts.iterator(); + X509Certificate cert = null; + if (it.hasNext()) { + cert = new X509CertImpl((org.apache.harmony.security.x509.Certificate)it.next()); + } else { + throw new SignatureException("signature contains no certificates"); + } + + List sigInfos = signedData.getSignerInfos(); + SignerInfo sigInfo; + if (!sigInfos.isEmpty()) { + sigInfo = (SignerInfo)sigInfos.get(0); + } else { + throw new IOException("no signer infos!"); + } + + // Check that the public key of the certificate contained + // in the package equals one of our trusted public keys. + + HashSet<Certificate> trusted = getTrustedCerts( + deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile); + + PublicKey signatureKey = cert.getPublicKey(); + boolean verified = false; + for (Certificate c : trusted) { + if (c.getPublicKey().equals(signatureKey)) { + verified = true; + break; + } + } + if (!verified) { + throw new SignatureException("signature doesn't match any trusted key"); + } + + // The signature cert matches a trusted key. Now verify that + // the digest in the cert matches the actual file data. + + // The verifier in recovery *only* handles SHA1withRSA + // signatures. SignApk.java always uses SHA1withRSA, no + // matter what the cert says to use. Ignore + // cert.getSigAlgName(), and instead use whatever + // algorithm is used by the signature (which should be + // SHA1withRSA). + + String da = sigInfo.getdigestAlgorithm(); + String dea = sigInfo.getDigestEncryptionAlgorithm(); + String alg = null; + if (da == null || dea == null) { + // fall back to the cert algorithm if the sig one + // doesn't look right. + alg = cert.getSigAlgName(); + } else { + alg = da + "with" + dea; + } + Signature sig = Signature.getInstance(alg); + sig.initVerify(cert); + + // The signature covers all of the OTA package except the + // archive comment and its 2-byte length. + long toRead = fileLen - commentSize - 2; + long soFar = 0; + raf.seek(0); + byte[] buffer = new byte[4096]; + while (soFar < toRead) { + int size = buffer.length; + if (soFar + size > toRead) { + size = (int)(toRead - soFar); + } + int read = raf.read(buffer, 0, size); + sig.update(buffer, 0, read); + soFar += read; + + if (listener != null) { + long now = System.currentTimeMillis(); + int p = (int)(soFar * 100 / toRead); + if (p > lastPercent && + now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { + lastPercent = p; + lastPublishTime = now; + listener.onProgress(lastPercent); + } + } + } + if (listener != null) { + listener.onProgress(100); + } + + if (!sig.verify(sigInfo.getEncryptedDigest())) { + throw new SignatureException("signature digest verification failed"); + } + } finally { + raf.close(); + } + } + + /** + * Reboots the device in order to install the given update + * package. + * Requires the {@link android.Manifest.permission#REBOOT} + * and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM} + * permissions. + * + * @param context the Context to use + * @param packageFile the update package to install. Currently + * must be on the /cache or /data partitions. + * + * @throws IOException if writing the recovery command file + * fails, or if the reboot itself fails. + */ + public static void installPackage(Context context, File packageFile) + throws IOException { + String filename = packageFile.getCanonicalPath(); + + if (filename.startsWith("/cache/")) { + filename = "CACHE:" + filename.substring(7); + } else if (filename.startsWith("/data/")) { + filename = "DATA:" + filename.substring(6); + } else { + throw new IllegalArgumentException( + "Must start with /cache or /data: " + filename); + } + Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); + String arg = "--update_package=" + filename; + bootCommand(context, arg); + } + + /** + * Reboots the device and wipes the user data partition. This is + * sometimes called a "factory reset", which is something of a + * misnomer because the system partition is not restored to its + * factory state. + * Requires the {@link android.Manifest.permission#REBOOT} + * and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM} + * permissions. + * + * @param context the Context to use + * + * @throws IOException if writing the recovery command file + * fails, or if the reboot itself fails. + */ + public static void rebootWipeUserData(Context context) + throws IOException { + bootCommand(context, "--wipe_data"); + } + + /** + * Reboot into the recovery system to wipe the /data partition and toggle + * Encrypted File Systems on/off. + * @param extras to add to the RECOVERY_COMPLETED intent after rebooting. + * @throws IOException if something goes wrong. + * + * @hide + */ + public static void rebootToggleEFS(Context context, boolean efsEnabled) + throws IOException { + if (efsEnabled) { + bootCommand(context, "--set_encrypted_filesystem=on"); + } else { + bootCommand(context, "--set_encrypted_filesystem=off"); + } + } + + /** + * Reboot into the recovery system with the supplied argument. + * @param arg to pass to the recovery utility. + * @throws IOException if something goes wrong. + */ + private static void bootCommand(Context context, String arg) throws IOException { + RECOVERY_DIR.mkdirs(); // In case we need it + COMMAND_FILE.delete(); // In case it's not writable + LOG_FILE.delete(); + + FileWriter command = new FileWriter(COMMAND_FILE); + try { + command.write(arg); + command.write("\n"); + } finally { + command.close(); + } + + // Having written the command file, go ahead and reboot + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + pm.reboot("recovery"); + + throw new IOException("Reboot failed (no permissions?)"); + } + + /** + * Called after booting to process and remove recovery-related files. + * @return the log file from recovery, or null if none was found. + * + * @hide + */ + public static String handleAftermath() { + // Record the tail of the LOG_FILE + String log = null; + try { + log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n"); + } catch (FileNotFoundException e) { + Log.i(TAG, "No recovery log file"); + } catch (IOException e) { + Log.e(TAG, "Error reading recovery log", e); + } + + // Delete everything in RECOVERY_DIR + String[] names = RECOVERY_DIR.list(); + for (int i = 0; names != null && i < names.length; i++) { + File f = new File(RECOVERY_DIR, names[i]); + if (!f.delete()) { + Log.e(TAG, "Can't delete: " + f); + } else { + Log.i(TAG, "Deleted: " + f); + } + } + + return log; + } + + private void RecoverySystem() { } // Do not instantiate +} diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java index 4eaea6a..389c9f4 100644 --- a/core/java/android/pim/vcard/VCardComposer.java +++ b/core/java/android/pim/vcard/VCardComposer.java @@ -54,6 +54,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; +import java.lang.reflect.Method; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import java.util.Arrays; @@ -197,7 +198,7 @@ public class VCardComposer { if (mIsDoCoMo) { try { // Create one empty entry. - mWriter.write(createOneEntryInternal("-1")); + mWriter.write(createOneEntryInternal("-1", null)); } catch (IOException e) { Log.e(LOG_TAG, "IOException occurred during exportOneContactData: " @@ -428,6 +429,14 @@ public class VCardComposer { } public boolean createOneEntry() { + return createOneEntry(null); + } + + /** + * @param getEntityIteratorMethod For Dependency Injection. + * @hide just for testing. + */ + public boolean createOneEntry(Method getEntityIteratorMethod) { if (mCursor == null || mCursor.isAfterLast()) { mErrorReason = FAILURE_REASON_NOT_INITIALIZED; return false; @@ -439,7 +448,8 @@ public class VCardComposer { vcard = createOneCallLogEntryInternal(); } else { if (mIdColumn >= 0) { - vcard = createOneEntryInternal(mCursor.getString(mIdColumn)); + vcard = createOneEntryInternal(mCursor.getString(mIdColumn), + getEntityIteratorMethod); } else { Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn); return true; @@ -475,7 +485,8 @@ public class VCardComposer { return true; } - private String createOneEntryInternal(final String contactId) { + private String createOneEntryInternal(final String contactId, + Method getEntityIteratorMethod) { final Map<String, List<ContentValues>> contentValuesListMap = new HashMap<String, List<ContentValues>>(); // The resolver may return the entity iterator with no data. It is possiible. @@ -484,13 +495,34 @@ public class VCardComposer { boolean dataExists = false; EntityIterator entityIterator = null; try { - final Uri uri = RawContacts.CONTENT_URI.buildUpon() - .appendEncodedPath(contactId) - .appendEncodedPath(RawContacts.Entity.CONTENT_DIRECTORY) - .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1") - .build(); - entityIterator = RawContacts.newEntityIterator(mContentResolver.query( - uri, null, null, null, null)); + + if (getEntityIteratorMethod != null) { + try { + final Uri uri = RawContacts.CONTENT_URI.buildUpon() + .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1") + .build(); + final String selection = Data.CONTACT_ID + "=?"; + final String[] selectionArgs = new String[] {contactId}; + entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null, + mContentResolver, uri, selection, selectionArgs, null); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + final Uri uri = RawContacts.CONTENT_URI.buildUpon() + .appendEncodedPath(contactId) + .appendEncodedPath(RawContacts.Entity.CONTENT_DIRECTORY) + .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1") + .build(); + entityIterator = RawContacts.newEntityIterator(mContentResolver.query( + uri, null, null, null, null)); + } + + if (entityIterator == null) { + Log.e(LOG_TAG, "EntityIterator is null"); + return ""; + } + dataExists = entityIterator.hasNext(); while (entityIterator.hasNext()) { Entity entity = entityIterator.next(); diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java index b74564a..a94b3b4 100644 --- a/core/java/android/provider/Calendar.java +++ b/core/java/android/provider/Calendar.java @@ -1059,11 +1059,23 @@ public final class Calendar { * <P>Type: INTEGER</P> */ public static final String MAX_BUSYBITS = "maxBusyBits"; + + /** + * The minimum Julian day in the EventDays table. + * <P>Type: INTEGER</P> + */ + public static final String MIN_EVENTDAYS = "minEventDays"; + + /** + * The maximum Julian day in the EventDays table. + * <P>Type: INTEGER</P> + */ + public static final String MAX_EVENTDAYS = "maxEventDays"; } public static final class CalendarMetaData implements CalendarMetaDataColumns { } - + /*busybits*/ public interface BusyBitsColumns { /** * The Julian day number. @@ -1088,6 +1100,7 @@ public final class Calendar { public static final String ALL_DAY_COUNT = "allDayCount"; } + /*busybits*/ public static final class BusyBits implements BusyBitsColumns { public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when"); @@ -1102,6 +1115,47 @@ public final class Calendar { /** * Retrieves the busy bits for the Julian days starting at "startDay" * for "numDays". + * This is being phased out so has been changed to an empty method so + * that it doesn't reference anything that needs to be cleaned up else- + * where. + * + * @param cr the ContentResolver + * @param startDay the first Julian day in the range + * @param numDays the number of days to load (must be at least 1) + * @return a database cursor + */ + public static final Cursor query(ContentResolver cr, int startDay, int numDays) { +// if (numDays < 1) { +// return null; +// } +// int endDay = startDay + numDays - 1; +// Uri.Builder builder = CONTENT_URI.buildUpon(); +// ContentUris.appendId(builder, startDay); +// ContentUris.appendId(builder, endDay); +// return cr.query(builder.build(), PROJECTION, null /* selection */, +// null /* selection args */, DAY); + return null; + } + } + + public interface EventDaysColumns { + /** + * The Julian starting day number. + * <P>Type: INTEGER (int)</P> + */ + public static final String STARTDAY = "startDay"; + + } + + public static final class EventDays implements EventDaysColumns { + public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/groupbyday"); + + public static final String[] PROJECTION = { STARTDAY }; + public static final String SELECTION = "selected==1"; + + /** + * Retrieves the days with events for the Julian days starting at "startDay" + * for "numDays". * * @param cr the ContentResolver * @param startDay the first Julian day in the range @@ -1116,8 +1170,8 @@ public final class Calendar { Uri.Builder builder = CONTENT_URI.buildUpon(); ContentUris.appendId(builder, startDay); ContentUris.appendId(builder, endDay); - return cr.query(builder.build(), PROJECTION, null /* selection */, - null /* selection args */, DAY); + return cr.query(builder.build(), PROJECTION, SELECTION, + null /* selection args */, STARTDAY); } } diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index 1a38166..a29ecb5 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -1334,8 +1334,26 @@ public class Contacts { } /** + * TODO find a place to put the canonical version of these. + */ + interface ProviderNames { + // + //NOTE: update Contacts.java with new providers when they're added. + // + String YAHOO = "Yahoo"; + String GTALK = "GTalk"; + String MSN = "MSN"; + String ICQ = "ICQ"; + String AIM = "AIM"; + String XMPP = "XMPP"; + String JABBER = "JABBER"; + String SKYPE = "SKYPE"; + String QQ = "QQ"; + } + + /** * This looks up the provider name defined in - * {@link android.provider.Im.ProviderNames} from the predefined IM protocol id. + * from the predefined IM protocol id. * This is used for interacting with the IM application. * * @param protocol the protocol ID @@ -1348,21 +1366,21 @@ public class Contacts { public static String lookupProviderNameFromId(int protocol) { switch (protocol) { case PROTOCOL_GOOGLE_TALK: - return Im.ProviderNames.GTALK; + return ProviderNames.GTALK; case PROTOCOL_AIM: - return Im.ProviderNames.AIM; + return ProviderNames.AIM; case PROTOCOL_MSN: - return Im.ProviderNames.MSN; + return ProviderNames.MSN; case PROTOCOL_YAHOO: - return Im.ProviderNames.YAHOO; + return ProviderNames.YAHOO; case PROTOCOL_ICQ: - return Im.ProviderNames.ICQ; + return ProviderNames.ICQ; case PROTOCOL_JABBER: - return Im.ProviderNames.JABBER; + return ProviderNames.JABBER; case PROTOCOL_SKYPE: - return Im.ProviderNames.SKYPE; + return ProviderNames.SKYPE; case PROTOCOL_QQ: - return Im.ProviderNames.QQ; + return ProviderNames.QQ; } return null; } @@ -1532,7 +1550,35 @@ public class Contacts { * @deprecated see {@link android.provider.ContactsContract} */ @Deprecated - public interface PresenceColumns extends Im.CommonPresenceColumns { + public interface PresenceColumns { + /** + * The priority, an integer, used by XMPP presence + * <P>Type: INTEGER</P> + */ + String PRIORITY = "priority"; + + /** + * The server defined status. + * <P>Type: INTEGER (one of the values below)</P> + */ + String PRESENCE_STATUS = ContactsContract.StatusUpdates.PRESENCE; + + /** + * Presence Status definition + */ + int OFFLINE = ContactsContract.StatusUpdates.OFFLINE; + int INVISIBLE = ContactsContract.StatusUpdates.INVISIBLE; + int AWAY = ContactsContract.StatusUpdates.AWAY; + int IDLE = ContactsContract.StatusUpdates.IDLE; + int DO_NOT_DISTURB = ContactsContract.StatusUpdates.DO_NOT_DISTURB; + int AVAILABLE = ContactsContract.StatusUpdates.AVAILABLE; + + /** + * The user defined status line. + * <P>Type: TEXT</P> + */ + String PRESENCE_CUSTOM_STATUS = ContactsContract.StatusUpdates.STATUS; + /** * The IM service the presence is coming from. Formatted using either * {@link Contacts.ContactMethods#encodePredefinedImProtocol} or diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index e98d8ee..ac8bf91 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -35,6 +35,7 @@ import android.graphics.Rect; import android.net.Uri; import android.os.RemoteException; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.Pair; import android.view.View; @@ -294,7 +295,7 @@ public final class ContactsContract { * The display name for the contact. * <P>Type: TEXT</P> */ - public static final String DISPLAY_NAME = "display_name"; + public static final String DISPLAY_NAME = ContactNameColumns.DISPLAY_NAME_PRIMARY; /** * Reference to the row in the RawContacts table holding the contact name. @@ -456,13 +457,13 @@ public final class ContactsContract { * The default text shown as the contact's display name. It is based on * available data, see {@link #DISPLAY_NAME_SOURCE}. */ - public static final String DISPLAY_NAME = "display_name"; + public static final String DISPLAY_NAME_PRIMARY = "display_name"; /** * Alternative representation of the display name. If display name is * based on the structured name and the structured name follows * the Western full name style, then this field contains the "family name first" - * version of the full name. Otherwise, it is the same as {@link #DISPLAY_NAME}. + * version of the full name. Otherwise, it is the same as DISPLAY_NAME_PRIMARY. */ public static final String DISPLAY_NAME_ALTERNATIVE = "display_name_alt"; @@ -483,7 +484,7 @@ public final class ContactsContract { * the sort key is the name's Pinyin spelling; for Japanese names * it is the Hiragana version of the phonetic name. */ - public static final String SORT_KEY = "sort_key"; + public static final String SORT_KEY_PRIMARY = "sort_key"; /** * Sort key based on the alternative representation of the full name, @@ -562,7 +563,7 @@ public final class ContactsContract { * </tr> * <tr> * <td>String</td> - * <td>{@link #DISPLAY_NAME}</td> + * <td>DISPLAY_NAME_PRIMARY</td> * <td>read-only</td> * <td>The display name for the contact. It is the display name * contributed by the raw contact referred to by the NAME_RAW_CONTACT_ID @@ -692,7 +693,7 @@ public final class ContactsContract { * </table> */ public static class Contacts implements BaseColumns, ContactsColumns, - ContactOptionsColumns, ContactStatusColumns { + ContactOptionsColumns, ContactNameColumns, ContactStatusColumns { /** * This utility class cannot be instantiated */ @@ -1519,6 +1520,7 @@ public final class ContactsContract { super(cursor); } + @Override public android.content.Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException { final int columnRawContactId = cursor.getColumnIndexOrThrow(RawContacts._ID); @@ -1601,18 +1603,39 @@ public final class ContactsContract { * @see StatusUpdates * @see ContactsContract.Data */ - protected interface StatusColumns extends Im.CommonPresenceColumns { + protected interface StatusColumns { /** * Contact's latest presence level. * <P>Type: INTEGER (one of the values below)</P> */ - public static final String PRESENCE = PRESENCE_STATUS; + public static final String PRESENCE = "mode"; + + /** + * @deprecated use {@link #PRESENCE} + */ + @Deprecated + public static final String PRESENCE_STATUS = PRESENCE; + + /* + * Presence Status definition + */ + int OFFLINE = 0; + int INVISIBLE = 1; + int AWAY = 2; + int IDLE = 3; + int DO_NOT_DISTURB = 4; + int AVAILABLE = 5; /** * Contact latest status update. * <p>Type: TEXT</p> */ - public static final String STATUS = PRESENCE_CUSTOM_STATUS; + public static final String STATUS = "status"; + + /** + * @deprecated use {@link #STATUS} + */ + public static final String PRESENCE_CUSTOM_STATUS = STATUS; /** * The absolute time in milliseconds when the latest status was inserted/updated. @@ -1736,8 +1759,8 @@ public final class ContactsContract { * @see ContactsContract.Data */ protected interface DataColumnsWithJoins extends BaseColumns, DataColumns, StatusColumns, - RawContactsColumns, ContactsColumns, ContactOptionsColumns, ContactStatusColumns { - + RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns, + ContactStatusColumns { } /** @@ -3018,7 +3041,7 @@ public final class ContactsContract { /** * The alphabet used for capturing the phonetic name. - * See {@link ContactsContract.PhoneticNameStyle}. + * See ContactsContract.PhoneticNameStyle. * @hide */ public static final String PHONETIC_NAME_STYLE = DATA11; @@ -3901,6 +3924,12 @@ public final class ContactsContract { * <td>{@link #DATA9}</td> * <td></td> * </tr> + * <tr> + * <td>String</td> + * <td>PHONETIC_NAME_STYLE</td> + * <td>{@link #DATA10}</td> + * <td></td> + * </tr> * </table> */ public static final class Organization implements DataColumnsWithJoins, CommonColumns { @@ -3958,6 +3987,13 @@ public final class ContactsContract { public static final String OFFICE_LOCATION = DATA9; /** + * The alphabet used for capturing the phonetic name. + * See {@link ContactsContract.PhoneticNameStyle}. + * @hide + */ + public static final String PHONETIC_NAME_STYLE = DATA10; + + /** * Return the string resource that best describes the given * {@link #TYPE}. Will always return a valid resource. */ @@ -4578,6 +4614,7 @@ public final class ContactsContract { super(cursor); } + @Override public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException { // we expect the cursor is already at the row we need to read from final ContentValues values = new ContentValues(); @@ -4860,8 +4897,10 @@ public final class ContactsContract { /** * Extra used to specify pivot dialog location in screen coordinates. + * @deprecated Use {@link Intent#setSourceBounds(Rect)} instead. * @hide */ + @Deprecated public static final String EXTRA_TARGET_RECT = "target_rect"; /** @@ -4921,15 +4960,17 @@ public final class ContactsContract { */ public static void showQuickContact(Context context, View target, Uri lookupUri, int mode, String[] excludeMimes) { - // Find location and bounds of target view - final int[] location = new int[2]; - target.getLocationOnScreen(location); + // Find location and bounds of target view, adjusting based on the + // assumed local density. + final float appScale = context.getResources().getCompatibilityInfo().applicationScale; + final int[] pos = new int[2]; + target.getLocationOnScreen(pos); final Rect rect = new Rect(); - rect.left = location[0]; - rect.top = location[1]; - rect.right = rect.left + target.getWidth(); - rect.bottom = rect.top + target.getHeight(); + rect.left = (int) (pos[0] * appScale + 0.5f); + rect.top = (int) (pos[1] * appScale + 0.5f); + rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f); + rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f); // Trigger with obtained rectangle showQuickContact(context, rect, lookupUri, mode, excludeMimes); @@ -4946,8 +4987,11 @@ public final class ContactsContract { * @param target Specific {@link Rect} that this dialog should be * centered around, in screen coordinates. In particular, if * the dialog has a "callout" arrow, it will be pointed and - * centered around this {@link Rect}. - * @param lookupUri A {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style + * centered around this {@link Rect}. If you are running at a + * non-native density, you need to manually adjust using + * {@link DisplayMetrics#density} before calling. + * @param lookupUri A + * {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style * {@link Uri} that describes a specific contact to feature * in this dialog. * @param mode Any of {@link #MODE_SMALL}, {@link #MODE_MEDIUM}, or @@ -4966,7 +5010,7 @@ public final class ContactsContract { | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); intent.setData(lookupUri); - intent.putExtra(EXTRA_TARGET_RECT, target); + intent.setSourceBounds(target); intent.putExtra(EXTRA_MODE, mode); intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes); context.startActivity(intent); diff --git a/core/java/android/provider/Im.java b/core/java/android/provider/Im.java deleted file mode 100644 index ff52199..0000000 --- a/core/java/android/provider/Im.java +++ /dev/null @@ -1,2358 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.provider; - -import android.content.ContentQueryMap; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.os.Handler; - -import java.util.HashMap; - -/** - * The GTalk provider stores all information about roster contacts, chat messages, presence, etc. - * - * @hide - */ -public class Im { - /** - * no public constructor since this is a utility class - */ - private Im() {} - - /** - * The Columns for IM providers - */ - public interface ProviderColumns { - /** - * The name of the IM provider - * <P>Type: TEXT</P> - */ - String NAME = "name"; - - /** - * The full name of the provider - * <P>Type: TEXT</P> - */ - String FULLNAME = "fullname"; - - /** - * The category for the provider, used to form intent. - * <P>Type: TEXT</P> - */ - String CATEGORY = "category"; - - /** - * The url users should visit to create a new account for this provider - * <P>Type: TEXT</P> - */ - String SIGNUP_URL = "signup_url"; - } - - /** - * Known names corresponding to the {@link ProviderColumns#NAME} column - */ - public interface ProviderNames { - // - //NOTE: update Contacts.java with new providers when they're added. - // - String YAHOO = "Yahoo"; - String GTALK = "GTalk"; - String MSN = "MSN"; - String ICQ = "ICQ"; - String AIM = "AIM"; - String XMPP = "XMPP"; - String JABBER = "JABBER"; - String SKYPE = "SKYPE"; - String QQ = "QQ"; - } - - /** - * This table contains the IM providers - */ - public static final class Provider implements BaseColumns, ProviderColumns { - private Provider() {} - - public static final long getProviderIdForName(ContentResolver cr, String providerName) { - String[] selectionArgs = new String[1]; - selectionArgs[0] = providerName; - - Cursor cursor = cr.query(CONTENT_URI, - PROVIDER_PROJECTION, - NAME+"=?", - selectionArgs, null); - - long retVal = 0; - try { - if (cursor.moveToFirst()) { - retVal = cursor.getLong(cursor.getColumnIndexOrThrow(_ID)); - } - } finally { - cursor.close(); - } - - return retVal; - } - - public static final String getProviderNameForId(ContentResolver cr, long providerId) { - Cursor cursor = cr.query(CONTENT_URI, - PROVIDER_PROJECTION, - _ID + "=" + providerId, - null, null); - - String retVal = null; - try { - if (cursor.moveToFirst()) { - retVal = cursor.getString(cursor.getColumnIndexOrThrow(NAME)); - } - } finally { - cursor.close(); - } - - return retVal; - } - - private static final String[] PROVIDER_PROJECTION = new String[] { - _ID, - NAME - }; - - public static final String ACTIVE_ACCOUNT_ID = "account_id"; - public static final String ACTIVE_ACCOUNT_USERNAME = "account_username"; - public static final String ACTIVE_ACCOUNT_PW = "account_pw"; - public static final String ACTIVE_ACCOUNT_LOCKED = "account_locked"; - public static final String ACTIVE_ACCOUNT_KEEP_SIGNED_IN = "account_keepSignedIn"; - public static final String ACCOUNT_PRESENCE_STATUS = "account_presenceStatus"; - public static final String ACCOUNT_CONNECTION_STATUS = "account_connStatus"; - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/providers"); - - public static final Uri CONTENT_URI_WITH_ACCOUNT = - Uri.parse("content://com.google.android.providers.talk/providers/account"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-providers"; - - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-providers"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "name ASC"; - } - - /** - * The columns for IM accounts. There can be more than one account for each IM provider. - */ - public interface AccountColumns { - /** - * The name of the account - * <P>Type: TEXT</P> - */ - String NAME = "name"; - - /** - * The IM provider for this account - * <P>Type: INTEGER</P> - */ - String PROVIDER = "provider"; - - /** - * The username for this account - * <P>Type: TEXT</P> - */ - String USERNAME = "username"; - - /** - * The password for this account - * <P>Type: TEXT</P> - */ - String PASSWORD = "pw"; - - /** - * A boolean value indicates if the account is active. - * <P>Type: INTEGER</P> - */ - String ACTIVE = "active"; - - /** - * A boolean value indicates if the account is locked (not editable) - * <P>Type: INTEGER</P> - */ - String LOCKED = "locked"; - - /** - * A boolean value to indicate whether this account is kept signed in. - * <P>Type: INTEGER</P> - */ - String KEEP_SIGNED_IN = "keep_signed_in"; - - /** - * A boolean value indiciating the last login state for this account - * <P>Type: INTEGER</P> - */ - String LAST_LOGIN_STATE = "last_login_state"; - } - - /** - * This table contains the IM accounts. - */ - public static final class Account implements BaseColumns, AccountColumns { - private Account() {} - - public static final long getProviderIdForAccount(ContentResolver cr, long accountId) { - Cursor cursor = cr.query(CONTENT_URI, - PROVIDER_PROJECTION, - _ID + "=" + accountId, - null /* selection args */, - null /* sort order */); - - long providerId = 0; - - try { - if (cursor.moveToFirst()) { - providerId = cursor.getLong(PROVIDER_COLUMN); - } - } finally { - cursor.close(); - } - - return providerId; - } - - private static final String[] PROVIDER_PROJECTION = new String[] { PROVIDER }; - private static final int PROVIDER_COLUMN = 0; - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/accounts"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * account. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-accounts"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * account. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-accounts"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "name ASC"; - - } - - /** - * Connection status - */ - public interface ConnectionStatus { - /** - * The connection is offline, not logged in. - */ - int OFFLINE = 0; - - /** - * The connection is attempting to connect. - */ - int CONNECTING = 1; - - /** - * The connection is suspended due to network not available. - */ - int SUSPENDED = 2; - - /** - * The connection is logged in and online. - */ - int ONLINE = 3; - } - - public interface AccountStatusColumns { - /** - * account id - * <P>Type: INTEGER</P> - */ - String ACCOUNT = "account"; - - /** - * User's presence status, see definitions in {#link CommonPresenceColumn} - * <P>Type: INTEGER</P> - */ - String PRESENCE_STATUS = "presenceStatus"; - - /** - * The connection status of this account, see {#link ConnectionStatus} - * <P>Type: INTEGER</P> - */ - String CONNECTION_STATUS = "connStatus"; - } - - public static final class AccountStatus implements BaseColumns, AccountStatusColumns { - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/accountStatus"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of account status. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-account-status"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single account status. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-account-status"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "name ASC"; - } - - /** - * Columns from the Contacts table. - */ - public interface ContactsColumns { - /** - * The username - * <P>Type: TEXT</P> - */ - String USERNAME = "username"; - - /** - * The nickname or display name - * <P>Type: TEXT</P> - */ - String NICKNAME = "nickname"; - - /** - * The IM provider for this contact - * <P>Type: INTEGER</P> - */ - String PROVIDER = "provider"; - - /** - * The account (within a IM provider) for this contact - * <P>Type: INTEGER</P> - */ - String ACCOUNT = "account"; - - /** - * The contactList this contact belongs to - * <P>Type: INTEGER</P> - */ - String CONTACTLIST = "contactList"; - - /** - * Contact type - * <P>Type: INTEGER</P> - */ - String TYPE = "type"; - - /** - * normal IM contact - */ - int TYPE_NORMAL = 0; - /** - * temporary contact, someone not in the list of contacts that we - * subscribe presence for. Usually created because of the user is - * having a chat session with this contact. - */ - int TYPE_TEMPORARY = 1; - /** - * temporary contact created for group chat. - */ - int TYPE_GROUP = 2; - /** - * blocked contact. - */ - int TYPE_BLOCKED = 3; - /** - * the contact is hidden. The client should always display this contact to the user. - */ - int TYPE_HIDDEN = 4; - /** - * the contact is pinned. The client should always display this contact to the user. - */ - int TYPE_PINNED = 5; - - /** - * Contact subscription status - * <P>Type: INTEGER</P> - */ - String SUBSCRIPTION_STATUS = "subscriptionStatus"; - - /** - * no pending subscription - */ - int SUBSCRIPTION_STATUS_NONE = 0; - /** - * requested to subscribe - */ - int SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING = 1; - /** - * requested to unsubscribe - */ - int SUBSCRIPTION_STATUS_UNSUBSCRIBE_PENDING = 2; - - /** - * Contact subscription type - * <P>Type: INTEGER </P> - */ - String SUBSCRIPTION_TYPE = "subscriptionType"; - - /** - * The user and contact have no interest in each other's presence. - */ - int SUBSCRIPTION_TYPE_NONE = 0; - /** - * The user wishes to stop receiving presence updates from the contact. - */ - int SUBSCRIPTION_TYPE_REMOVE = 1; - /** - * The user is interested in receiving presence updates from the contact. - */ - int SUBSCRIPTION_TYPE_TO = 2; - /** - * The contact is interested in receiving presence updates from the user. - */ - int SUBSCRIPTION_TYPE_FROM = 3; - /** - * The user and contact have a mutual interest in each other's presence. - */ - int SUBSCRIPTION_TYPE_BOTH = 4; - /** - * This is a special type reserved for pending subscription requests - */ - int SUBSCRIPTION_TYPE_INVITATIONS = 5; - - /** - * Quick Contact: derived from Google Contact Extension's "message_count" attribute. - * <P>Type: INTEGER</P> - */ - String QUICK_CONTACT = "qc"; - - /** - * Google Contact Extension attribute - * - * Rejected: a boolean value indicating whether a subscription request from - * this client was ever rejected by the user. "true" indicates that it has. - * This is provided so that a client can block repeated subscription requests. - * <P>Type: INTEGER</P> - */ - String REJECTED = "rejected"; - - /** - * Off The Record status: 0 for disabled, 1 for enabled - * <P>Type: INTEGER </P> - */ - String OTR = "otr"; - } - - /** - * This defines the different type of values of {@link ContactsColumns#OTR} - */ - public interface OffTheRecordType { - /* - * Off the record not turned on - */ - int DISABLED = 0; - /** - * Off the record turned on, but we don't know who turned it on - */ - int ENABLED = 1; - /** - * Off the record turned on by the user - */ - int ENABLED_BY_USER = 2; - /** - * Off the record turned on by the buddy - */ - int ENABLED_BY_BUDDY = 3; - }; - - /** - * This table contains contacts. - */ - public static final class Contacts implements BaseColumns, - ContactsColumns, PresenceColumns, ChatsColumns { - /** - * no public constructor since this is a utility class - */ - private Contacts() {} - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/contacts"); - - /** - * The content:// style URL for contacts joined with presence - */ - public static final Uri CONTENT_URI_WITH_PRESENCE = - Uri.parse("content://com.google.android.providers.talk/contactsWithPresence"); - - /** - * The content:// style URL for barebone contacts, not joined with any other table - */ - public static final Uri CONTENT_URI_CONTACTS_BAREBONE = - Uri.parse("content://com.google.android.providers.talk/contactsBarebone"); - - /** - * The content:// style URL for contacts who have an open chat session - */ - public static final Uri CONTENT_URI_CHAT_CONTACTS = - Uri.parse("content://com.google.android.providers.talk/contacts_chatting"); - - /** - * The content:// style URL for contacts who have been blocked - */ - public static final Uri CONTENT_URI_BLOCKED_CONTACTS = - Uri.parse("content://com.google.android.providers.talk/contacts/blocked"); - - /** - * The content:// style URL for contacts by provider and account - */ - public static final Uri CONTENT_URI_CONTACTS_BY = - Uri.parse("content://com.google.android.providers.talk/contacts"); - - /** - * The content:// style URL for contacts by provider and account, - * and who have an open chat session - */ - public static final Uri CONTENT_URI_CHAT_CONTACTS_BY = - Uri.parse("content://com.google.android.providers.talk/contacts/chatting"); - - /** - * The content:// style URL for contacts by provider and account, - * and who are online - */ - public static final Uri CONTENT_URI_ONLINE_CONTACTS_BY = - Uri.parse("content://com.google.android.providers.talk/contacts/online"); - - /** - * The content:// style URL for contacts by provider and account, - * and who are offline - */ - public static final Uri CONTENT_URI_OFFLINE_CONTACTS_BY = - Uri.parse("content://com.google.android.providers.talk/contacts/offline"); - - /** - * The content:// style URL for operations on bulk contacts - */ - public static final Uri BULK_CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/bulk_contacts"); - - /** - * The content:// style URL for the count of online contacts in each - * contact list by provider and account. - */ - public static final Uri CONTENT_URI_ONLINE_COUNT = - Uri.parse("content://com.google.android.providers.talk/contacts/onlineCount"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. - */ - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-contacts"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * person. - */ - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/gtalk-contacts"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = - "subscriptionType DESC, last_message_date DESC," + - " mode DESC, nickname COLLATE UNICODE ASC"; - - public static final String CHATS_CONTACT = "chats_contact"; - - public static final String AVATAR_HASH = "avatars_hash"; - - public static final String AVATAR_DATA = "avatars_data"; - } - - /** - * Columns from the ContactList table. - */ - public interface ContactListColumns { - String NAME = "name"; - String PROVIDER = "provider"; - String ACCOUNT = "account"; - } - - /** - * This table contains the contact lists. - */ - public static final class ContactList implements BaseColumns, - ContactListColumns { - private ContactList() {} - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/contactLists"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-contactLists"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * person. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-contactLists"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "name COLLATE UNICODE ASC"; - - public static final String PROVIDER_NAME = "provider_name"; - - public static final String ACCOUNT_NAME = "account_name"; - } - - /** - * Columns from the BlockedList table. - */ - public interface BlockedListColumns { - /** - * The username of the blocked contact. - * <P>Type: TEXT</P> - */ - String USERNAME = "username"; - - /** - * The nickname of the blocked contact. - * <P>Type: TEXT</P> - */ - String NICKNAME = "nickname"; - - /** - * The provider id of the blocked contact. - * <P>Type: INT</P> - */ - String PROVIDER = "provider"; - - /** - * The account id of the blocked contact. - * <P>Type: INT</P> - */ - String ACCOUNT = "account"; - } - - /** - * This table contains blocked lists - */ - public static final class BlockedList implements BaseColumns, BlockedListColumns { - private BlockedList() {} - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/blockedList"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-blockedList"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * person. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-blockedList"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "nickname ASC"; - - public static final String PROVIDER_NAME = "provider_name"; - - public static final String ACCOUNT_NAME = "account_name"; - - public static final String AVATAR_DATA = "avatars_data"; - } - - /** - * Columns from the contactsEtag table - */ - public interface ContactsEtagColumns { - /** - * The roster etag, computed by the server, stored on the client. There is one etag - * per account roster. - * <P>Type: TEXT</P> - */ - String ETAG = "etag"; - - /** - * The OTR etag, computed by the server, stored on the client. There is one OTR etag - * per account roster. - * <P>Type: TEXT</P> - */ - String OTR_ETAG = "otr_etag"; - - /** - * The account id for the etag. - * <P> Type: INTEGER </P> - */ - String ACCOUNT = "account"; - } - - public static final class ContactsEtag implements BaseColumns, ContactsEtagColumns { - private ContactsEtag() {} - - public static final Cursor query(ContentResolver cr, - String[] projection) { - return cr.query(CONTENT_URI, projection, null, null, null); - } - - public static final Cursor query(ContentResolver cr, - String[] projection, String where, String orderBy) { - return cr.query(CONTENT_URI, projection, where, - null, orderBy == null ? null : orderBy); - } - - public static final String getRosterEtag(ContentResolver resolver, long accountId) { - String retVal = null; - - Cursor c = resolver.query(CONTENT_URI, - CONTACT_ETAG_PROJECTION, - ACCOUNT + "=" + accountId, - null /* selection args */, - null /* sort order */); - - try { - if (c.moveToFirst()) { - retVal = c.getString(COLUMN_ETAG); - } - } finally { - c.close(); - } - - return retVal; - } - - public static final String getOtrEtag(ContentResolver resolver, long accountId) { - String retVal = null; - - Cursor c = resolver.query(CONTENT_URI, - CONTACT_OTR_ETAG_PROJECTION, - ACCOUNT + "=" + accountId, - null /* selection args */, - null /* sort order */); - - try { - if (c.moveToFirst()) { - retVal = c.getString(COLUMN_OTR_ETAG); - } - } finally { - c.close(); - } - - return retVal; - } - - private static final String[] CONTACT_ETAG_PROJECTION = new String[] { - Im.ContactsEtag.ETAG // 0 - }; - - private static int COLUMN_ETAG = 0; - - private static final String[] CONTACT_OTR_ETAG_PROJECTION = new String[] { - Im.ContactsEtag.OTR_ETAG // 0 - }; - - private static int COLUMN_OTR_ETAG = 0; - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/contactsEtag"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-contactsEtag"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * person. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-contactsEtag"; - } - - /** - * Message type definition - */ - public interface MessageType { - /* sent message */ - int OUTGOING = 0; - /* received message */ - int INCOMING = 1; - /* presence became available */ - int PRESENCE_AVAILABLE = 2; - /* presence became away */ - int PRESENCE_AWAY = 3; - /* presence became DND (busy) */ - int PRESENCE_DND = 4; - /* presence became unavailable */ - int PRESENCE_UNAVAILABLE = 5; - /* the message is converted to a group chat */ - int CONVERT_TO_GROUPCHAT = 6; - /* generic status */ - int STATUS = 7; - /* the message cannot be sent now, but will be sent later */ - int POSTPONED = 8; - /* off The Record status is turned off */ - int OTR_IS_TURNED_OFF = 9; - /* off the record status is turned on */ - int OTR_IS_TURNED_ON = 10; - /* off the record status turned on by user */ - int OTR_TURNED_ON_BY_USER = 11; - /* off the record status turned on by buddy */ - int OTR_TURNED_ON_BY_BUDDY = 12; - } - - /** - * The common columns for messages table - */ - public interface MessageColumns { - /** - * The thread_id column stores the contact id of the contact the message belongs to. - * For groupchat messages, the thread_id stores the group id, which is the contact id - * of the temporary group contact created for the groupchat. So there should be no - * collision between groupchat message thread id and regular message thread id. - */ - String THREAD_ID = "thread_id"; - - /** - * The nickname. This is used for groupchat messages to indicate the participant's - * nickname. For non groupchat messages, this field should be left empty. - */ - String NICKNAME = "nickname"; - - /** - * The body - * <P>Type: TEXT</P> - */ - String BODY = "body"; - - /** - * The date this message is sent or received. This represents the display date for - * the message. - * <P>Type: INTEGER</P> - */ - String DATE = "date"; - - /** - * The real date for this message. While 'date' can be modified by the client - * to account for server time skew, the real_date is the original timestamp set - * by the server for incoming messages. - * <P>Type: INTEGER</P> - */ - String REAL_DATE = "real_date"; - - /** - * Message Type, see {@link MessageType} - * <P>Type: INTEGER</P> - */ - String TYPE = "type"; - - /** - * Error Code: 0 means no error. - * <P>Type: INTEGER </P> - */ - String ERROR_CODE = "err_code"; - - /** - * Error Message - * <P>Type: TEXT</P> - */ - String ERROR_MESSAGE = "err_msg"; - - /** - * Packet ID, auto assigned by the GTalkService for outgoing messages or the - * GTalk server for incoming messages. The packet id field is optional for messages, - * so it could be null. - * <P>Type: STRING</P> - */ - String PACKET_ID = "packet_id"; - - /** - * Is groupchat message or not - * <P>Type: INTEGER</P> - */ - String IS_GROUP_CHAT = "is_muc"; - - /** - * A hint that the UI should show the sent time of this message - * <P>Type: INTEGER</P> - */ - String DISPLAY_SENT_TIME = "show_ts"; - - /* - * For rows which have been consolidated this is the row id of the - * row into which they have been consolidated. - */ - String CONSOLIDATION_KEY = "consolidation_key"; - } - - /** - * This table contains messages. - */ - public static final class Messages implements BaseColumns, MessageColumns { - /** - * no public constructor since this is a utility class - */ - private Messages() {} - - /** - * Gets the Uri to query messages by thread id. - * - * @param threadId the thread id of the message. - * @return the Uri - */ - public static final Uri getContentUriByThreadId(long threadId) { - Uri.Builder builder = CONTENT_URI_MESSAGES_BY_THREAD_ID.buildUpon(); - ContentUris.appendId(builder, threadId); - return builder.build(); - } - - /** - * @deprecated - * - * Gets the Uri to query messages by account and contact. - * - * @param accountId the account id of the contact. - * @param username the user name of the contact. - * @return the Uri - */ - public static final Uri getContentUriByContact(long accountId, String username) { - Uri.Builder builder = CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT.buildUpon(); - ContentUris.appendId(builder, accountId); - builder.appendPath(username); - return builder.build(); - } - - /** - * Gets the Uri to query messages by provider. - * - * @param providerId the service provider id. - * @return the Uri - */ - public static final Uri getContentUriByProvider(long providerId) { - Uri.Builder builder = CONTENT_URI_MESSAGES_BY_PROVIDER.buildUpon(); - ContentUris.appendId(builder, providerId); - return builder.build(); - } - - /** - * Gets the Uri to query off the record messages by account. - * - * @param accountId the account id. - * @return the Uri - */ - public static final Uri getContentUriByAccount(long accountId) { - Uri.Builder builder = CONTENT_URI_BY_ACCOUNT.buildUpon(); - ContentUris.appendId(builder, accountId); - return builder.build(); - } - - /** - * Gets the Uri to query off the record messages by thread id. - * - * @param threadId the thread id of the message. - * @return the Uri - */ - public static final Uri getOtrMessagesContentUriByThreadId(long threadId) { - Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID.buildUpon(); - ContentUris.appendId(builder, threadId); - return builder.build(); - } - - /** - * @deprecated - * - * Gets the Uri to query off the record messages by account and contact. - * - * @param accountId the account id of the contact. - * @param username the user name of the contact. - * @return the Uri - */ - public static final Uri getOtrMessagesContentUriByContact(long accountId, String username) { - Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT.buildUpon(); - ContentUris.appendId(builder, accountId); - builder.appendPath(username); - return builder.build(); - } - - /** - * Gets the Uri to query off the record messages by provider. - * - * @param providerId the service provider id. - * @return the Uri - */ - public static final Uri getOtrMessagesContentUriByProvider(long providerId) { - Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_PROVIDER.buildUpon(); - ContentUris.appendId(builder, providerId); - return builder.build(); - } - - /** - * Gets the Uri to query off the record messages by account. - * - * @param accountId the account id. - * @return the Uri - */ - public static final Uri getOtrMessagesContentUriByAccount(long accountId) { - Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT.buildUpon(); - ContentUris.appendId(builder, accountId); - return builder.build(); - } - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/messages"); - - /** - * The content:// style URL for messages by thread id - */ - public static final Uri CONTENT_URI_MESSAGES_BY_THREAD_ID = - Uri.parse("content://com.google.android.providers.talk/messagesByThreadId"); - - /** - * The content:// style URL for messages by account and contact - */ - public static final Uri CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT = - Uri.parse("content://com.google.android.providers.talk/messagesByAcctAndContact"); - - /** - * The content:// style URL for messages by provider - */ - public static final Uri CONTENT_URI_MESSAGES_BY_PROVIDER = - Uri.parse("content://com.google.android.providers.talk/messagesByProvider"); - - /** - * The content:// style URL for messages by account - */ - public static final Uri CONTENT_URI_BY_ACCOUNT = - Uri.parse("content://com.google.android.providers.talk/messagesByAccount"); - - /** - * The content:// style url for off the record messages - */ - public static final Uri OTR_MESSAGES_CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/otrMessages"); - - /** - * The content:// style url for off the record messages by thread id - */ - public static final Uri OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID = - Uri.parse("content://com.google.android.providers.talk/otrMessagesByThreadId"); - - /** - * The content:// style url for off the record messages by account and contact - */ - public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT = - Uri.parse("content://com.google.android.providers.talk/otrMessagesByAcctAndContact"); - - /** - * The content:// style URL for off the record messages by provider - */ - public static final Uri OTR_MESSAGES_CONTENT_URI_BY_PROVIDER = - Uri.parse("content://com.google.android.providers.talk/otrMessagesByProvider"); - - /** - * The content:// style URL for off the record messages by account - */ - public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT = - Uri.parse("content://com.google.android.providers.talk/otrMessagesByAccount"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-messages"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * person. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-messages"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "date ASC"; - - /** - * The "contact" column. This is not a real column in the messages table, but a - * temoprary column created when querying for messages (joined with the contacts table) - */ - public static final String CONTACT = "contact"; - } - - /** - * Columns for the GroupMember table. - */ - public interface GroupMemberColumns { - /** - * The id of the group this member belongs to. - * <p>Type: INTEGER</p> - */ - String GROUP = "groupId"; - - /** - * The full name of this member. - * <p>Type: TEXT</p> - */ - String USERNAME = "username"; - - /** - * The nick name of this member. - * <p>Type: TEXT</p> - */ - String NICKNAME = "nickname"; - } - - public final static class GroupMembers implements GroupMemberColumns { - private GroupMembers(){} - - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/groupMembers"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * group members. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-groupMembers"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * group member. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-groupMembers"; - } - - /** - * Columns from the Invitation table. - */ - public interface InvitationColumns { - /** - * The provider id. - * <p>Type: INTEGER</p> - */ - String PROVIDER = "providerId"; - - /** - * The account id. - * <p>Type: INTEGER</p> - */ - String ACCOUNT = "accountId"; - - /** - * The invitation id. - * <p>Type: TEXT</p> - */ - String INVITE_ID = "inviteId"; - - /** - * The name of the sender of the invitation. - * <p>Type: TEXT</p> - */ - String SENDER = "sender"; - - /** - * The name of the group which the sender invite you to join. - * <p>Type: TEXT</p> - */ - String GROUP_NAME = "groupName"; - - /** - * A note - * <p>Type: TEXT</p> - */ - String NOTE = "note"; - - /** - * The current status of the invitation. - * <p>Type: TEXT</p> - */ - String STATUS = "status"; - - int STATUS_PENDING = 0; - int STATUS_ACCEPTED = 1; - int STATUS_REJECTED = 2; - } - - /** - * This table contains the invitations received from others. - */ - public final static class Invitation implements InvitationColumns, - BaseColumns { - private Invitation() { - } - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/invitations"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * invitations. - */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/gtalk-invitations"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * invitation. - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-invitations"; - } - - /** - * Columns from the Avatars table - */ - public interface AvatarsColumns { - /** - * The contact this avatar belongs to - * <P>Type: TEXT</P> - */ - String CONTACT = "contact"; - - String PROVIDER = "provider_id"; - - String ACCOUNT = "account_id"; - - /** - * The hash of the image data - * <P>Type: TEXT</P> - */ - String HASH = "hash"; - - /** - * raw image data - * <P>Type: BLOB</P> - */ - String DATA = "data"; - } - - /** - * This table contains avatars. - */ - public static final class Avatars implements BaseColumns, AvatarsColumns { - /** - * no public constructor since this is a utility class - */ - private Avatars() {} - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/avatars"); - - /** - * The content:// style URL for avatars by provider, account and contact - */ - public static final Uri CONTENT_URI_AVATARS_BY = - Uri.parse("content://com.google.android.providers.talk/avatarsBy"); - - /** - * The MIME type of {@link #CONTENT_URI} providing the avatars - */ - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-avatars"; - - /** - * The MIME type of a {@link #CONTENT_URI} - */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/gtalk-avatars"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "contact ASC"; - - } - - /** - * Common presence columns shared between the IM and contacts presence tables - */ - public interface CommonPresenceColumns { - /** - * The priority, an integer, used by XMPP presence - * <P>Type: INTEGER</P> - */ - String PRIORITY = "priority"; - - /** - * The server defined status. - * <P>Type: INTEGER (one of the values below)</P> - */ - String PRESENCE_STATUS = "mode"; - - /** - * Presence Status definition - */ - int OFFLINE = 0; - int INVISIBLE = 1; - int AWAY = 2; - int IDLE = 3; - int DO_NOT_DISTURB = 4; - int AVAILABLE = 5; - - /** - * The user defined status line. - * <P>Type: TEXT</P> - */ - String PRESENCE_CUSTOM_STATUS = "status"; - } - - /** - * Columns from the Presence table. - */ - public interface PresenceColumns extends CommonPresenceColumns { - /** - * The contact id - * <P>Type: INTEGER</P> - */ - String CONTACT_ID = "contact_id"; - - /** - * The contact's JID resource, only relevant for XMPP contact - * <P>Type: TEXT</P> - */ - String JID_RESOURCE = "jid_resource"; - - /** - * The contact's client type - */ - String CLIENT_TYPE = "client_type"; - - /** - * client type definitions - */ - int CLIENT_TYPE_DEFAULT = 0; - int CLIENT_TYPE_MOBILE = 1; - int CLIENT_TYPE_ANDROID = 2; - } - - /** - * Contains presence infomation for contacts. - */ - public static final class Presence implements BaseColumns, PresenceColumns { - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/presence"); - - /** - * The content URL for Talk presences for an account - */ - public static final Uri CONTENT_URI_BY_ACCOUNT = - Uri.parse("content://com.google.android.providers.talk/presence/account"); - - /** - * The content:// style URL for operations on bulk contacts - */ - public static final Uri BULK_CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/bulk_presence"); - - /** - * The content:// style URL for seeding presences for a given account id. - */ - public static final Uri SEED_PRESENCE_BY_ACCOUNT_CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/seed_presence/account"); - - /** - * The MIME type of a {@link #CONTENT_URI} providing a directory of presence - */ - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-presence"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "mode DESC"; - } - - /** - * Columns from the Chats table. - */ - public interface ChatsColumns { - /** - * The contact ID this chat belongs to. The value is a long. - * <P>Type: INT</P> - */ - String CONTACT_ID = "contact_id"; - - /** - * The GTalk JID resource. The value is a string. - * <P>Type: TEXT</P> - */ - String JID_RESOURCE = "jid_resource"; - - /** - * Whether this is a groupchat or not. - * <P>Type: INT</P> - */ - String GROUP_CHAT = "groupchat"; - - /** - * The last unread message. This both indicates that there is an - * unread message, and what the message is. - * <P>Type: TEXT</P> - */ - String LAST_UNREAD_MESSAGE = "last_unread_message"; - - /** - * The last message timestamp - * <P>Type: INT</P> - */ - String LAST_MESSAGE_DATE = "last_message_date"; - - /** - * A message that is being composed. This indicates that there was a - * message being composed when the chat screen was shutdown, and what the - * message is. - * <P>Type: TEXT</P> - */ - String UNSENT_COMPOSED_MESSAGE = "unsent_composed_message"; - - /** - * A value from 0-9 indicating which quick-switch chat screen slot this - * chat is occupying. If none (for instance, this is the 12th active chat) - * then the value is -1. - * <P>Type: INT</P> - */ - String SHORTCUT = "shortcut"; - } - - /** - * Contains ongoing chat sessions. - */ - public static final class Chats implements BaseColumns, ChatsColumns { - /** - * no public constructor since this is a utility class - */ - private Chats() {} - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/chats"); - - /** - * The content URL for all chats that belong to the account - */ - public static final Uri CONTENT_URI_BY_ACCOUNT = - Uri.parse("content://com.google.android.providers.talk/chats/account"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of chats. - */ - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-chats"; - - /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single chat. - */ - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/gtalk-chats"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "last_message_date ASC"; - } - - /** - * Columns from session cookies table. Used for IMPS. - */ - public static interface SessionCookiesColumns { - String NAME = "name"; - String VALUE = "value"; - String PROVIDER = "provider"; - String ACCOUNT = "account"; - } - - /** - * Contains IMPS session cookies. - */ - public static class SessionCookies implements SessionCookiesColumns, BaseColumns { - private SessionCookies() { - } - - /** - * The content:// style URI for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/sessionCookies"); - - /** - * The content:// style URL for session cookies by provider and account - */ - public static final Uri CONTENT_URI_SESSION_COOKIES_BY = - Uri.parse("content://com.google.android.providers.talk/sessionCookiesBy"); - - /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. - */ - public static final String CONTENT_TYPE = "vnd.android-dir/gtalk-sessionCookies"; - } - - /** - * Columns from ProviderSettings table - */ - public static interface ProviderSettingsColumns { - /** - * The id in database of the related provider - * - * <P>Type: INT</P> - */ - String PROVIDER = "provider"; - - /** - * The name of the setting - * <P>Type: TEXT</P> - */ - String NAME = "name"; - - /** - * The value of the setting - * <P>Type: TEXT</P> - */ - String VALUE = "value"; - } - - public static class ProviderSettings implements ProviderSettingsColumns { - private ProviderSettings() { - } - - /** - * The content:// style URI for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/providerSettings"); - - /** - * The MIME type of {@link #CONTENT_URI} providing provider settings - */ - public static final String CONTENT_TYPE = "vnd.android-dir/gtalk-providerSettings"; - - /** - * A boolean value to indicate whether this provider should show the offline contacts - */ - public static final String SHOW_OFFLINE_CONTACTS = "show_offline_contacts"; - - /** controls whether or not the GTalk service automatically connect to server. */ - public static final String SETTING_AUTOMATICALLY_CONNECT_GTALK = "gtalk_auto_connect"; - - /** controls whether or not the GTalk service will be automatically started after boot */ - public static final String SETTING_AUTOMATICALLY_START_SERVICE = "auto_start_service"; - - /** controls whether or not the offline contacts will be hided */ - public static final String SETTING_HIDE_OFFLINE_CONTACTS = "hide_offline_contacts"; - - /** controls whether or not enable the GTalk notification */ - public static final String SETTING_ENABLE_NOTIFICATION = "enable_notification"; - - /** specifies whether or not to vibrate */ - public static final String SETTING_VIBRATE = "vibrate"; - - /** specifies the Uri string of the ringtone */ - public static final String SETTING_RINGTONE = "ringtone"; - - /** specifies the Uri of the default ringtone */ - public static final String SETTING_RINGTONE_DEFAULT = - "content://settings/system/notification_sound"; - - /** specifies whether or not to show mobile indicator to friends */ - public static final String SETTING_SHOW_MOBILE_INDICATOR = "mobile_indicator"; - - /** specifies whether or not to show as away when device is idle */ - public static final String SETTING_SHOW_AWAY_ON_IDLE = "show_away_on_idle"; - - /** specifies whether or not to upload heartbeat stat upon login */ - public static final String SETTING_UPLOAD_HEARTBEAT_STAT = "upload_heartbeat_stat"; - - /** specifies the last heartbeat interval received from the server */ - public static final String SETTING_HEARTBEAT_INTERVAL = "heartbeat_interval"; - - /** specifiy the JID resource used for Google Talk connection */ - public static final String SETTING_JID_RESOURCE = "jid_resource"; - - /** - * Used for reliable message queue (RMQ). This is for storing the last rmq id received - * from the GTalk server - */ - public static final String LAST_RMQ_RECEIVED = "last_rmq_rec"; - - /** - * Query the settings of the provider specified by id - * - * @param cr - * the relative content resolver - * @param providerId - * the specified id of provider - * @return a HashMap which contains all the settings for the specified - * provider - */ - public static HashMap<String, String> queryProviderSettings(ContentResolver cr, - long providerId) { - HashMap<String, String> settings = new HashMap<String, String>(); - - String[] projection = { NAME, VALUE }; - Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), projection, null, null, null); - if (c == null) { - return null; - } - - while(c.moveToNext()) { - settings.put(c.getString(0), c.getString(1)); - } - - c.close(); - - return settings; - } - - /** - * Get the string value of setting which is specified by provider id and the setting name. - * - * @param cr The ContentResolver to use to access the settings table. - * @param providerId The id of the provider. - * @param settingName The name of the setting. - * @return The value of the setting if the setting exist, otherwise return null. - */ - public static String getStringValue(ContentResolver cr, long providerId, String settingName) { - String ret = null; - Cursor c = getSettingValue(cr, providerId, settingName); - if (c != null) { - ret = c.getString(0); - c.close(); - } - - return ret; - } - - /** - * Get the boolean value of setting which is specified by provider id and the setting name. - * - * @param cr The ContentResolver to use to access the settings table. - * @param providerId The id of the provider. - * @param settingName The name of the setting. - * @return The value of the setting if the setting exist, otherwise return false. - */ - public static boolean getBooleanValue(ContentResolver cr, long providerId, String settingName) { - boolean ret = false; - Cursor c = getSettingValue(cr, providerId, settingName); - if (c != null) { - ret = c.getInt(0) != 0; - c.close(); - } - return ret; - } - - private static Cursor getSettingValue(ContentResolver cr, long providerId, String settingName) { - Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), new String[]{VALUE}, NAME + "=?", - new String[]{settingName}, null); - if (c != null) { - if (!c.moveToFirst()) { - c.close(); - return null; - } - } - return c; - } - - /** - * Save a long value of setting in the table providerSetting. - * - * @param cr The ContentProvider used to access the providerSetting table. - * @param providerId The id of the provider. - * @param name The name of the setting. - * @param value The value of the setting. - */ - public static void putLongValue(ContentResolver cr, long providerId, String name, - long value) { - ContentValues v = new ContentValues(3); - v.put(PROVIDER, providerId); - v.put(NAME, name); - v.put(VALUE, value); - - cr.insert(CONTENT_URI, v); - } - - /** - * Save a boolean value of setting in the table providerSetting. - * - * @param cr The ContentProvider used to access the providerSetting table. - * @param providerId The id of the provider. - * @param name The name of the setting. - * @param value The value of the setting. - */ - public static void putBooleanValue(ContentResolver cr, long providerId, String name, - boolean value) { - ContentValues v = new ContentValues(3); - v.put(PROVIDER, providerId); - v.put(NAME, name); - v.put(VALUE, Boolean.toString(value)); - - cr.insert(CONTENT_URI, v); - } - - /** - * Save a string value of setting in the table providerSetting. - * - * @param cr The ContentProvider used to access the providerSetting table. - * @param providerId The id of the provider. - * @param name The name of the setting. - * @param value The value of the setting. - */ - public static void putStringValue(ContentResolver cr, long providerId, String name, - String value) { - ContentValues v = new ContentValues(3); - v.put(PROVIDER, providerId); - v.put(NAME, name); - v.put(VALUE, value); - - cr.insert(CONTENT_URI, v); - } - - /** - * A convenience method to set whether or not the GTalk service should be started - * automatically. - * - * @param contentResolver The ContentResolver to use to access the settings table - * @param autoConnect Whether the GTalk service should be started automatically. - */ - public static void setAutomaticallyConnectGTalk(ContentResolver contentResolver, - long providerId, boolean autoConnect) { - putBooleanValue(contentResolver, providerId, SETTING_AUTOMATICALLY_CONNECT_GTALK, - autoConnect); - } - - /** - * A convenience method to set whether or not the offline contacts should be hided - * - * @param contentResolver The ContentResolver to use to access the setting table - * @param hideOfflineContacts Whether the offline contacts should be hided - */ - public static void setHideOfflineContacts(ContentResolver contentResolver, - long providerId, boolean hideOfflineContacts) { - putBooleanValue(contentResolver, providerId, SETTING_HIDE_OFFLINE_CONTACTS, - hideOfflineContacts); - } - - /** - * A convenience method to set whether or not enable the GTalk notification. - * - * @param contentResolver The ContentResolver to use to access the setting table. - * @param enable Whether enable the GTalk notification - */ - public static void setEnableNotification(ContentResolver contentResolver, long providerId, - boolean enable) { - putBooleanValue(contentResolver, providerId, SETTING_ENABLE_NOTIFICATION, enable); - } - - /** - * A convenience method to set whether or not to vibrate. - * - * @param contentResolver The ContentResolver to use to access the setting table. - * @param vibrate Whether or not to vibrate - */ - public static void setVibrate(ContentResolver contentResolver, long providerId, - boolean vibrate) { - putBooleanValue(contentResolver, providerId, SETTING_VIBRATE, vibrate); - } - - /** - * A convenience method to set the Uri String of the ringtone. - * - * @param contentResolver The ContentResolver to use to access the setting table. - * @param ringtoneUri The Uri String of the ringtone to be set. - */ - public static void setRingtoneURI(ContentResolver contentResolver, long providerId, - String ringtoneUri) { - putStringValue(contentResolver, providerId, SETTING_RINGTONE, ringtoneUri); - } - - /** - * A convenience method to set whether or not to show mobile indicator. - * - * @param contentResolver The ContentResolver to use to access the setting table. - * @param showMobileIndicator Whether or not to show mobile indicator. - */ - public static void setShowMobileIndicator(ContentResolver contentResolver, long providerId, - boolean showMobileIndicator) { - putBooleanValue(contentResolver, providerId, SETTING_SHOW_MOBILE_INDICATOR, - showMobileIndicator); - } - - /** - * A convenience method to set whether or not to show as away when device is idle. - * - * @param contentResolver The ContentResolver to use to access the setting table. - * @param showAway Whether or not to show as away when device is idle. - */ - public static void setShowAwayOnIdle(ContentResolver contentResolver, - long providerId, boolean showAway) { - putBooleanValue(contentResolver, providerId, SETTING_SHOW_AWAY_ON_IDLE, showAway); - } - - /** - * A convenience method to set whether or not to upload heartbeat stat. - * - * @param contentResolver The ContentResolver to use to access the setting table. - * @param uploadStat Whether or not to upload heartbeat stat. - */ - public static void setUploadHeartbeatStat(ContentResolver contentResolver, - long providerId, boolean uploadStat) { - putBooleanValue(contentResolver, providerId, SETTING_UPLOAD_HEARTBEAT_STAT, uploadStat); - } - - /** - * A convenience method to set the heartbeat interval last received from the server. - * - * @param contentResolver The ContentResolver to use to access the setting table. - * @param interval The heartbeat interval last received from the server. - */ - public static void setHeartbeatInterval(ContentResolver contentResolver, - long providerId, long interval) { - putLongValue(contentResolver, providerId, SETTING_HEARTBEAT_INTERVAL, interval); - } - - /** - * A convenience method to set the jid resource. - */ - public static void setJidResource(ContentResolver contentResolver, - long providerId, String jidResource) { - putStringValue(contentResolver, providerId, SETTING_JID_RESOURCE, jidResource); - } - - public static class QueryMap extends ContentQueryMap { - private ContentResolver mContentResolver; - private long mProviderId; - - public QueryMap(ContentResolver contentResolver, long providerId, boolean keepUpdated, - Handler handlerForUpdateNotifications) { - super(contentResolver.query(CONTENT_URI, - new String[] {NAME,VALUE}, - PROVIDER + "=" + providerId, - null, // no selection args - null), // no sort order - NAME, keepUpdated, handlerForUpdateNotifications); - mContentResolver = contentResolver; - mProviderId = providerId; - } - - /** - * Set if the GTalk service should automatically connect to server. - * - * @param autoConnect if the GTalk service should auto connect to server. - */ - public void setAutomaticallyConnectToGTalkServer(boolean autoConnect) { - ProviderSettings.setAutomaticallyConnectGTalk(mContentResolver, mProviderId, - autoConnect); - } - - /** - * Check if the GTalk service should automatically connect to server. - * @return if the GTalk service should automatically connect to server. - */ - public boolean getAutomaticallyConnectToGTalkServer() { - return getBoolean(SETTING_AUTOMATICALLY_CONNECT_GTALK, - true /* default to automatically sign in */); - } - - /** - * Set whether or not the offline contacts should be hided. - * - * @param hideOfflineContacts Whether or not the offline contacts should be hided. - */ - public void setHideOfflineContacts(boolean hideOfflineContacts) { - ProviderSettings.setHideOfflineContacts(mContentResolver, mProviderId, - hideOfflineContacts); - } - - /** - * Check if the offline contacts should be hided. - * - * @return Whether or not the offline contacts should be hided. - */ - public boolean getHideOfflineContacts() { - return getBoolean(SETTING_HIDE_OFFLINE_CONTACTS, - false/* by default not hide the offline contacts*/); - } - - /** - * Set whether or not enable the GTalk notification. - * - * @param enable Whether or not enable the GTalk notification. - */ - public void setEnableNotification(boolean enable) { - ProviderSettings.setEnableNotification(mContentResolver, mProviderId, enable); - } - - /** - * Check if the GTalk notification is enabled. - * - * @return Whether or not enable the GTalk notification. - */ - public boolean getEnableNotification() { - return getBoolean(SETTING_ENABLE_NOTIFICATION, - true/* by default enable the notification */); - } - - /** - * Set whether or not to vibrate on GTalk notification. - * - * @param vibrate Whether or not to vibrate. - */ - public void setVibrate(boolean vibrate) { - ProviderSettings.setVibrate(mContentResolver, mProviderId, vibrate); - } - - /** - * Gets whether or not to vibrate on GTalk notification. - * - * @return Whether or not to vibrate. - */ - public boolean getVibrate() { - return getBoolean(SETTING_VIBRATE, false /* by default disable vibrate */); - } - - /** - * Set the Uri for the ringtone. - * - * @param ringtoneUri The Uri of the ringtone to be set. - */ - public void setRingtoneURI(String ringtoneUri) { - ProviderSettings.setRingtoneURI(mContentResolver, mProviderId, ringtoneUri); - } - - /** - * Get the Uri String of the current ringtone. - * - * @return The Uri String of the current ringtone. - */ - public String getRingtoneURI() { - return getString(SETTING_RINGTONE, SETTING_RINGTONE_DEFAULT); - } - - /** - * Set whether or not to show mobile indicator to friends. - * - * @param showMobile whether or not to show mobile indicator. - */ - public void setShowMobileIndicator(boolean showMobile) { - ProviderSettings.setShowMobileIndicator(mContentResolver, mProviderId, showMobile); - } - - /** - * Gets whether or not to show mobile indicator. - * - * @return Whether or not to show mobile indicator. - */ - public boolean getShowMobileIndicator() { - return getBoolean(SETTING_SHOW_MOBILE_INDICATOR, - true /* by default show mobile indicator */); - } - - /** - * Set whether or not to show as away when device is idle. - * - * @param showAway whether or not to show as away when device is idle. - */ - public void setShowAwayOnIdle(boolean showAway) { - ProviderSettings.setShowAwayOnIdle(mContentResolver, mProviderId, showAway); - } - - /** - * Get whether or not to show as away when device is idle. - * - * @return Whether or not to show as away when device is idle. - */ - public boolean getShowAwayOnIdle() { - return getBoolean(SETTING_SHOW_AWAY_ON_IDLE, - true /* by default show as away on idle*/); - } - - /** - * Set whether or not to upload heartbeat stat. - * - * @param uploadStat whether or not to upload heartbeat stat. - */ - public void setUploadHeartbeatStat(boolean uploadStat) { - ProviderSettings.setUploadHeartbeatStat(mContentResolver, mProviderId, uploadStat); - } - - /** - * Get whether or not to upload heartbeat stat. - * - * @return Whether or not to upload heartbeat stat. - */ - public boolean getUploadHeartbeatStat() { - return getBoolean(SETTING_UPLOAD_HEARTBEAT_STAT, - false /* by default do not upload */); - } - - /** - * Set the last received heartbeat interval from the server. - * - * @param interval the last received heartbeat interval from the server. - */ - public void setHeartbeatInterval(long interval) { - ProviderSettings.setHeartbeatInterval(mContentResolver, mProviderId, interval); - } - - /** - * Get the last received heartbeat interval from the server. - * - * @return the last received heartbeat interval from the server. - */ - public long getHeartbeatInterval() { - return getLong(SETTING_HEARTBEAT_INTERVAL, 0L /* an invalid default interval */); - } - - /** - * Set the JID resource. - * - * @param jidResource the jid resource to be stored. - */ - public void setJidResource(String jidResource) { - ProviderSettings.setJidResource(mContentResolver, mProviderId, jidResource); - } - /** - * Get the JID resource used for the Google Talk connection - * - * @return the JID resource stored. - */ - public String getJidResource() { - return getString(SETTING_JID_RESOURCE, null); - } - - /** - * Convenience function for retrieving a single settings value - * as a boolean. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * @return The setting's current value, or 'def' if it is not defined. - */ - private boolean getBoolean(String name, boolean def) { - ContentValues values = getValues(name); - return values != null ? values.getAsBoolean(VALUE) : def; - } - - /** - * Convenience function for retrieving a single settings value - * as a String. - * - * @param name The name of the setting to retrieve. - * @param def The value to return if the setting is not defined. - * @return The setting's current value or 'def' if it is not defined. - */ - private String getString(String name, String def) { - ContentValues values = getValues(name); - return values != null ? values.getAsString(VALUE) : def; - } - - /** - * Convenience function for retrieving a single settings value - * as an Integer. - * - * @param name The name of the setting to retrieve. - * @param def The value to return if the setting is not defined. - * @return The setting's current value or 'def' if it is not defined. - */ - private int getInteger(String name, int def) { - ContentValues values = getValues(name); - return values != null ? values.getAsInteger(VALUE) : def; - } - - /** - * Convenience function for retrieving a single settings value - * as a Long. - * - * @param name The name of the setting to retrieve. - * @param def The value to return if the setting is not defined. - * @return The setting's current value or 'def' if it is not defined. - */ - private long getLong(String name, long def) { - ContentValues values = getValues(name); - return values != null ? values.getAsLong(VALUE) : def; - } - } - - } - - - /** - * Columns for GTalk branding resource map cache table. This table caches the result of - * loading the branding resources to speed up GTalk landing page start. - */ - public interface BrandingResourceMapCacheColumns { - /** - * The provider ID - * <P>Type: INTEGER</P> - */ - String PROVIDER_ID = "provider_id"; - /** - * The application resource ID - * <P>Type: INTEGER</P> - */ - String APP_RES_ID = "app_res_id"; - /** - * The plugin resource ID - * <P>Type: INTEGER</P> - */ - String PLUGIN_RES_ID = "plugin_res_id"; - } - - /** - * The table for caching the result of loading GTalk branding resources. - */ - public static final class BrandingResourceMapCache - implements BaseColumns, BrandingResourceMapCacheColumns { - /** - * The content:// style URL for this table. - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/brandingResMapCache"); - } - - - - /** - * //TODO: move these to MCS specific provider. - * The following are MCS stuff, and should really live in a separate provider specific to - * MCS code. - */ - - /** - * Columns from OutgoingRmq table - */ - public interface OutgoingRmqColumns { - String RMQ_ID = "rmq_id"; - String TIMESTAMP = "ts"; - String DATA = "data"; - String PROTOBUF_TAG = "type"; - } - - /** - * //TODO: we should really move these to their own provider and database. - * The table for storing outgoing rmq packets. - */ - public static final class OutgoingRmq implements BaseColumns, OutgoingRmqColumns { - private static String[] RMQ_ID_PROJECTION = new String[] { - RMQ_ID, - }; - - /** - * queryHighestRmqId - * - * @param resolver the content resolver - * @return the highest rmq id assigned to the rmq packet, or 0 if there are no rmq packets - * in the OutgoingRmq table. - */ - public static final long queryHighestRmqId(ContentResolver resolver) { - Cursor cursor = resolver.query(Im.OutgoingRmq.CONTENT_URI_FOR_HIGHEST_RMQ_ID, - RMQ_ID_PROJECTION, - null, // selection - null, // selection args - null // sort - ); - - long retVal = 0; - try { - //if (DBG) log("initializeRmqid: cursor.count= " + cursor.count()); - - if (cursor.moveToFirst()) { - retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID)); - } - } finally { - cursor.close(); - } - - return retVal; - } - - /** - * The content:// style URL for this table. - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/outgoingRmqMessages"); - - /** - * The content:// style URL for the highest rmq id for the outgoing rmq messages - */ - public static final Uri CONTENT_URI_FOR_HIGHEST_RMQ_ID = - Uri.parse("content://com.google.android.providers.talk/outgoingHighestRmqId"); - - /** - * The default sort order for this table. - */ - public static final String DEFAULT_SORT_ORDER = "rmq_id ASC"; - } - - /** - * Columns for the LastRmqId table, which stores a single row for the last client rmq id - * sent to the server. - */ - public interface LastRmqIdColumns { - String RMQ_ID = "rmq_id"; - } - - /** - * //TODO: move these out into their own provider and database - * The table for storing the last client rmq id sent to the server. - */ - public static final class LastRmqId implements BaseColumns, LastRmqIdColumns { - private static String[] PROJECTION = new String[] { - RMQ_ID, - }; - - /** - * queryLastRmqId - * - * queries the last rmq id saved in the LastRmqId table. - * - * @param resolver the content resolver. - * @return the last rmq id stored in the LastRmqId table, or 0 if not found. - */ - public static final long queryLastRmqId(ContentResolver resolver) { - Cursor cursor = resolver.query(Im.LastRmqId.CONTENT_URI, - PROJECTION, - null, // selection - null, // selection args - null // sort - ); - - long retVal = 0; - try { - if (cursor.moveToFirst()) { - retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID)); - } - } finally { - cursor.close(); - } - - return retVal; - } - - /** - * saveLastRmqId - * - * saves the rmqId to the lastRmqId table. This will override the existing row if any, - * as we only keep one row of data in this table. - * - * @param resolver the content resolver. - * @param rmqId the rmq id to be saved. - */ - public static final void saveLastRmqId(ContentResolver resolver, long rmqId) { - ContentValues values = new ContentValues(); - - // always replace the first row. - values.put(_ID, 1); - values.put(RMQ_ID, rmqId); - resolver.insert(CONTENT_URI, values); - } - - /** - * The content:// style URL for this table. - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/lastRmqId"); - } - - /** - * Columns for the s2dRmqIds table, which stores the server-to-device message - * persistent ids. These are used in the RMQ2 protocol, where in the login request, the - * client selective acks these s2d ids to the server. - */ - public interface ServerToDeviceRmqIdsColumn { - String RMQ_ID = "rmq_id"; - } - - public static final class ServerToDeviceRmqIds implements BaseColumns, - ServerToDeviceRmqIdsColumn { - - /** - * The content:// style URL for this table. - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.google.android.providers.talk/s2dids"); - } - -} diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index ae53dbe2..6d1fef6 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -179,6 +179,13 @@ public final class MediaStore { public final static String EXTRA_OUTPUT = "output"; /** + * The string that is used when a media attribute is not known. For example, + * if an audio file does not have any meta data, the artist and album columns + * will be set to this value. + */ + public static final String UNKNOWN_STRING = "<unknown>"; + + /** * Common fields for most MediaProvider tables */ @@ -844,7 +851,6 @@ public final class MediaStore { * The position, in ms, playback was at when playback for this file * was last stopped. * <P>Type: INTEGER (long)</P> - * @hide */ public static final String BOOKMARK = "bookmark"; @@ -923,7 +929,6 @@ public final class MediaStore { /** * Non-zero if the audio file is a podcast * <P>Type: INTEGER (boolean)</P> - * @hide */ public static final String IS_PODCAST = "is_podcast"; @@ -964,7 +969,7 @@ public final class MediaStore { public static String keyFor(String name) { if (name != null) { boolean sortfirst = false; - if (name.equals(android.media.MediaFile.UNKNOWN_STRING)) { + if (name.equals(UNKNOWN_STRING)) { return "\001"; } // Check if the first character is \001. We use this to @@ -1258,7 +1263,6 @@ public final class MediaStore { * @param from The position of the item to move * @param to The position to move the item to * @return true on success - * @hide */ public static final boolean moveItem(ContentResolver res, long playlistId, int from, int to) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8c83303..112ea8f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1194,6 +1194,12 @@ public final class Settings { public static final String VOLUME_NOTIFICATION = "volume_notification"; /** + * Bluetooth Headset volume. This is used internally, changing this value will + * not change the volume. See AudioManager. + */ + public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco"; + + /** * Whether the notifications should use the ring volume (value of 1) or * a separate notification volume (value of 0). In most cases, users * will have this enabled so the notification and ringer volumes will be @@ -1214,7 +1220,7 @@ public final class Settings { */ public static final String[] VOLUME_SETTINGS = { VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, VOLUME_MUSIC, - VOLUME_ALARM, VOLUME_NOTIFICATION + VOLUME_ALARM, VOLUME_NOTIFICATION, VOLUME_BLUETOOTH_SCO }; /** @@ -1447,7 +1453,6 @@ public final class Settings { */ public static final String[] SETTINGS_TO_BACKUP = { STAY_ON_WHILE_PLUGGED_IN, - END_BUTTON_BEHAVIOR, WIFI_SLEEP_POLICY, WIFI_USE_STATIC_IP, WIFI_STATIC_IP, @@ -1472,12 +1477,14 @@ public final class Settings { VOLUME_MUSIC, VOLUME_ALARM, VOLUME_NOTIFICATION, + VOLUME_BLUETOOTH_SCO, VOLUME_VOICE + APPEND_FOR_LAST_AUDIBLE, VOLUME_SYSTEM + APPEND_FOR_LAST_AUDIBLE, VOLUME_RING + APPEND_FOR_LAST_AUDIBLE, VOLUME_MUSIC + APPEND_FOR_LAST_AUDIBLE, VOLUME_ALARM + APPEND_FOR_LAST_AUDIBLE, VOLUME_NOTIFICATION + APPEND_FOR_LAST_AUDIBLE, + VOLUME_BLUETOOTH_SCO + APPEND_FOR_LAST_AUDIBLE, TEXT_AUTO_REPLACE, TEXT_AUTO_CAPS, TEXT_AUTO_PUNCTUATE, @@ -3711,6 +3718,11 @@ public final class Settings { "dropbox:"; /** + * Nonzero causes Log.wtf() to crash. + */ + public static final String WTF_IS_FATAL = "wtf_is_fatal"; + + /** * The length of time in milli-seconds that automatic small adjustments to * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded. */ diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index f2e132b..816ae14 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -96,11 +96,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { BluetoothDevice.ERROR); switch(bondState) { case BluetoothDevice.BOND_BONDED: - setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); + if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) { + setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); + } break; case BluetoothDevice.BOND_BONDING: case BluetoothDevice.BOND_NONE: - setSinkPriority(device, BluetoothA2dp.PRIORITY_OFF); + setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED); break; } } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { @@ -305,7 +307,11 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return false; // State is DISCONNECTED + handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING); + if (!connectSinkNative(path)) { + // Restore previous state + handleSinkStateChange(device, mAudioDevices.get(device), state); return false; } return true; @@ -321,7 +327,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return false; } - switch (getSinkState(device)) { + int state = getSinkState(device); + switch (state) { case BluetoothA2dp.STATE_DISCONNECTED: return false; case BluetoothA2dp.STATE_DISCONNECTING: @@ -329,11 +336,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } // State is CONNECTING or CONNECTED or PLAYING + handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING); if (!disconnectSinkNative(path)) { + // Restore previous state + handleSinkStateChange(device, mAudioDevices.get(device), state); return false; - } else { - return true; } + return true; } public synchronized boolean suspendSink(BluetoothDevice device) { @@ -512,6 +521,19 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return result; } + private void onConnectSinkResult(String deviceObjectPath, boolean result) { + // If the call was a success, ignore we will update the state + // when we a Sink Property Change + if (!result) { + if (deviceObjectPath != null) { + String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); + BluetoothDevice device = mAdapter.getRemoteDevice(address); + int state = getSinkState(device); + handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); + } + } + } + @Override protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mAudioDevices.isEmpty()) return; diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 0d0d245..b28cf43 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -21,14 +21,15 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; -import android.os.ParcelUuid; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Message; +import android.os.ParcelUuid; import android.util.Log; import java.util.HashMap; +import java.util.Set; /** * TODO: Move this to @@ -553,7 +554,7 @@ class BluetoothEventLoop { if (mBluetoothService.isEnabled() && (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) && - (a2dp.getNonDisconnectedSinks().size() == 0)) { + !isOtherSinkInNonDisconnectingState(address)) { BluetoothDevice device = mAdapter.getRemoteDevice(address); authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; if (authorized) { @@ -568,6 +569,16 @@ class BluetoothEventLoop { return authorized; } + boolean isOtherSinkInNonDisconnectingState(String address) { + BluetoothA2dp a2dp = new BluetoothA2dp(mContext); + Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks(); + if (devices.size() == 0) return false; + for(BluetoothDevice dev: devices) { + if (!dev.getAddress().equals(address)) return true; + } + return false; + } + private void onAgentCancel() { Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index b590449..dfb775f 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -74,6 +74,7 @@ public class BluetoothService extends IBluetooth.Stub { private int mNativeData; private BluetoothEventLoop mEventLoop; private boolean mIsAirplaneSensitive; + private boolean mIsAirplaneToggleable; private int mBluetoothState; private boolean mRestart = false; // need to call enable() after disable() private boolean mIsDiscovering; @@ -370,7 +371,7 @@ public class BluetoothService extends IBluetooth.Stub { "Need BLUETOOTH_ADMIN permission"); // Airplane mode can prevent Bluetooth radio from being turned on. - if (mIsAirplaneSensitive && isAirplaneModeOn()) { + if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) { return false; } if (mBluetoothState != BluetoothAdapter.STATE_OFF) { @@ -545,7 +546,7 @@ public class BluetoothService extends IBluetooth.Stub { mEventLoop.onPropertyChanged(propVal); } - if (mIsAirplaneSensitive && isAirplaneModeOn()) { + if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) { disable(false); } @@ -1597,10 +1598,17 @@ public class BluetoothService extends IBluetooth.Stub { }; private void registerForAirplaneMode(IntentFilter filter) { - String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(), + final ContentResolver resolver = mContext.getContentResolver(); + final String airplaneModeRadios = Settings.System.getString(resolver, Settings.System.AIRPLANE_MODE_RADIOS); - mIsAirplaneSensitive = airplaneModeRadios == null - ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH); + final String toggleableRadios = Settings.System.getString(resolver, + Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + + mIsAirplaneSensitive = airplaneModeRadios == null ? true : + airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH); + mIsAirplaneToggleable = toggleableRadios == null ? false : + toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH); + if (mIsAirplaneSensitive) { filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); } @@ -1661,6 +1669,7 @@ public class BluetoothService extends IBluetooth.Stub { } pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive); + pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable); pw.println("Local address = " + getAddress()); pw.println("Local name = " + getName()); diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java index 2fc906a..862305b 100644 --- a/core/java/android/text/AutoText.java +++ b/core/java/android/text/AutoText.java @@ -18,7 +18,7 @@ package android.text; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import android.view.View; import org.xmlpull.v1.XmlPullParser; diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 380e5fd..33ecc01 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -46,7 +46,7 @@ import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Log; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import java.io.IOException; import java.io.StringReader; diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 0d04b13..7b307f8 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -247,10 +247,11 @@ implements MovementMethod KeyEvent.META_SHIFT_ON) == 1) || (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0); + int x = (int) event.getX(); + int y = (int) event.getY(); + int offset = getOffset(x, y, widget); + if (cap) { - int x = (int) event.getX(); - int y = (int) event.getY(); - int offset = getOffset(x, y, widget); buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT); @@ -260,6 +261,30 @@ implements MovementMethod // without this, users would get booted out of select // mode once the view detected it needed to scroll. widget.getParent().requestDisallowInterceptTouchEvent(true); + } else { + OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(), + OnePointFiveTapState.class); + + if (tap.length > 0) { + if (event.getEventTime() - tap[0].mWhen <= + ViewConfiguration.getDoubleTapTimeout() && + sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) { + + tap[0].active = true; + MetaKeyKeyListener.startSelecting(widget, buffer); + widget.getParent().requestDisallowInterceptTouchEvent(true); + buffer.setSpan(LAST_TAP_DOWN, offset, offset, + Spannable.SPAN_POINT_POINT); + } + + tap[0].mWhen = event.getEventTime(); + } else { + OnePointFiveTapState newtap = new OnePointFiveTapState(); + newtap.mWhen = event.getEventTime(); + newtap.active = false; + buffer.setSpan(newtap, 0, buffer.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } } } else if (event.getAction() == MotionEvent.ACTION_MOVE ) { boolean cap = (MetaKeyKeyListener.getMetaState(buffer, @@ -287,7 +312,7 @@ implements MovementMethod // user started the selection) int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN); - // Compute the selection boundries + // Compute the selection boundaries int spanstart; int spanend; if (offset >= lastDownOffset) { @@ -324,6 +349,19 @@ implements MovementMethod // XXX should do the same adjust for x as we do for the line. + OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(), + OnePointFiveTapState.class); + if (onepointfivetap.length > 0 && onepointfivetap[0].active && + Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) { + // If we've set select mode, because there was a onepointfivetap, + // but there was no ensuing swipe gesture, undo the select mode + // and remove reference to the last onepointfivetap. + MetaKeyKeyListener.stopSelecting(widget, buffer); + for (int i=0; i < onepointfivetap.length; i++) { + buffer.removeSpan(onepointfivetap[i]); + } + buffer.removeSpan(LAST_TAP_DOWN); + } boolean cap = (MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) || (MetaKeyKeyListener.getMetaState(buffer, @@ -335,10 +373,10 @@ implements MovementMethod if (tap.length > 0) { if (event.getEventTime() - tap[0].mWhen <= - ViewConfiguration.getDoubleTapTimeout()) { - if (sameWord(buffer, off, Selection.getSelectionEnd(buffer))) { - doubletap = true; - } + ViewConfiguration.getDoubleTapTimeout() && + sameWord(buffer, off, Selection.getSelectionEnd(buffer))) { + + doubletap = true; } tap[0].mWhen = event.getEventTime(); @@ -351,6 +389,11 @@ implements MovementMethod if (cap) { buffer.removeSpan(LAST_TAP_DOWN); + if (onepointfivetap.length > 0 && onepointfivetap[0].active) { + // If we selecting something with the onepointfivetap-and + // swipe gesture, stop it on finger up. + MetaKeyKeyListener.stopSelecting(widget, buffer); + } } else if (doubletap) { Selection.setSelection(buffer, findWordStart(buffer, off), @@ -373,6 +416,19 @@ implements MovementMethod long mWhen; } + /* We check for a onepointfive tap. This is similar to + * doubletap gesture (where a finger goes down, up, down, up, in a short + * time period), except in the onepointfive tap, a users finger only needs + * to go down, up, down in a short time period. We detect this type of tap + * to implement the onepointfivetap-and-swipe selection gesture. + * This gesture allows users to select a segment of text without going + * through the "select text" option in the context menu. + */ + private static class OnePointFiveTapState implements NoCopySpan { + long mWhen; + boolean active; + } + private static boolean sameWord(CharSequence text, int one, int two) { int start = findWordStart(text, one); int end = findWordEnd(text, one); diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index 81dd96e..ce0b954 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -16,134 +16,43 @@ package android.util; -import com.google.android.collect.Lists; - +import java.io.BufferedReader; +import java.io.FileReader; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * {@hide} - * Dynamically defined (in terms of event types), space efficient (i.e. "tight") event logging - * to help instrument code for large scale stability and performance monitoring. - * - * Note that this class contains all static methods. This is done for efficiency reasons. + * Access to the system diagnostic event record. System diagnostic events are + * used to record certain system-level events (such as garbage collection, + * activity manager state, system watchdogs, and other low level activity), + * which may be automatically collected and analyzed during system development. * - * Events for the event log are self-describing binary data structures. They start with a 20 byte - * header (generated automatically) which contains all of the following in order: + * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})! + * These diagnostic events are for system integrators, not application authors. * - * <ul> - * <li> Payload length: 2 bytes - length of the non-header portion </li> - * <li> Padding: 2 bytes - no meaning at this time </li> - * <li> Timestamp: - * <ul> - * <li> Seconds: 4 bytes - seconds since Epoch </li> - * <li> Nanoseconds: 4 bytes - plus extra nanoseconds </li> - * </ul></li> - * <li> Process ID: 4 bytes - matching {@link android.os.Process#myPid} </li> - * <li> Thread ID: 4 bytes - matching {@link android.os.Process#myTid} </li> - * </li> - * </ul> + * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags. + * They carry a payload of one or more int, long, or String values. The + * event-log-tags file defines the payload contents for each type code. * - * The above is followed by a payload, comprised of the following: - * <ul> - * <li> Tag: 4 bytes - unique integer used to identify a particular event. This number is also - * used as a key to map to a string that can be displayed by log reading tools. - * </li> - * <li> Type: 1 byte - can be either {@link #INT}, {@link #LONG}, {@link #STRING}, - * or {@link #LIST}. </li> - * <li> Event log value: the size and format of which is one of: - * <ul> - * <li> INT: 4 bytes </li> - * <li> LONG: 8 bytes </li> - * <li> STRING: - * <ul> - * <li> Size of STRING: 4 bytes </li> - * <li> The string: n bytes as specified in the size fields above. </li> - * </ul></li> - * <li> {@link List LIST}: - * <ul> - * <li> Num items: 1 byte </li> - * <li> N value payloads, where N is the number of items specified above. </li> - * </ul></li> - * </ul> - * </li> - * <li> '\n': 1 byte - an automatically generated newline, used to help detect and recover from log - * corruption and enable standard unix tools like grep, tail and wc to operate - * on event logs. </li> - * </ul> - * - * Note that all output is done in the endian-ness of the device (as determined - * by {@link ByteOrder#nativeOrder()}). + * @pending */ - public class EventLog { + private static final String TAG = "EventLog"; - // Value types - public static final byte INT = 0; - public static final byte LONG = 1; - public static final byte STRING = 2; - public static final byte LIST = 3; + private static final String TAGS_FILE = "/system/etc/event-log-tags"; + private static final String COMMENT_PATTERN = "^\\s*(#.*)?$"; + private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$"; + private static HashMap<String, Integer> sTagCodes = null; + private static HashMap<Integer, String> sTagNames = null; - /** - * An immutable tuple used to log a heterogeneous set of loggable items. - * The items can be Integer, Long, String, or {@link List}. - * The maximum number of items is 127 - */ - public static final class List { - private Object[] mItems; - - /** - * Get a particular tuple item - * @param pos The position of the item in the tuple - */ - public final Object getItem(int pos) { - return mItems[pos]; - } - - /** - * Get the number of items in the tuple. - */ - public final byte getNumItems() { - return (byte) mItems.length; - } - - /** - * Create a new tuple. - * @param items The items to create the tuple with, as varargs. - * @throws IllegalArgumentException if the arguments are too few (0), - * too many, or aren't loggable types. - */ - public List(Object... items) throws IllegalArgumentException { - if (items.length > Byte.MAX_VALUE) { - throw new IllegalArgumentException( - "A List must have fewer than " - + Byte.MAX_VALUE + " items in it."); - } - for (int i = 0; i < items.length; i++) { - final Object item = items[i]; - if (item == null) { - // Would be nice to be able to write null strings... - items[i] = ""; - } else if (!(item instanceof List || - item instanceof String || - item instanceof Integer || - item instanceof Long)) { - throw new IllegalArgumentException( - "Attempt to create a List with illegal item type."); - } - } - this.mItems = items; - } - } - - /** - * A previously logged event read from the logs. - */ + /** A previously logged event read from the logs. */ public static final class Event { private final ByteBuffer mBuffer; @@ -158,77 +67,83 @@ public class EventLog { private static final int TAG_OFFSET = 20; private static final int DATA_START = 24; + // Value types + private static final byte INT_TYPE = 0; + private static final byte LONG_TYPE = 1; + private static final byte STRING_TYPE = 2; + private static final byte LIST_TYPE = 3; + /** @param data containing event, read from the system */ - public Event(byte[] data) { + /*package*/ Event(byte[] data) { mBuffer = ByteBuffer.wrap(data); mBuffer.order(ByteOrder.nativeOrder()); } + /** @return the process ID which wrote the log entry */ public int getProcessId() { return mBuffer.getInt(PROCESS_OFFSET); } + /** @return the thread ID which wrote the log entry */ public int getThreadId() { return mBuffer.getInt(THREAD_OFFSET); } + /** @return the wall clock time when the entry was written */ public long getTimeNanos() { return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l + mBuffer.getInt(NANOSECONDS_OFFSET); } + /** @return the type tag code of the entry */ public int getTag() { return mBuffer.getInt(TAG_OFFSET); } - /** @return one of Integer, Long, String, or List. */ + /** @return one of Integer, Long, String, null, or Object[] of same. */ public synchronized Object getData() { - mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET)); - mBuffer.position(DATA_START); // Just after the tag. - return decodeObject(); - } - - public byte[] getRawData() { - return mBuffer.array(); + try { + mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET)); + mBuffer.position(DATA_START); // Just after the tag. + return decodeObject(); + } catch (IllegalArgumentException e) { + Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e); + return null; + } catch (BufferUnderflowException e) { + Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e); + return null; + } } /** @return the loggable item at the current position in mBuffer. */ private Object decodeObject() { - if (mBuffer.remaining() < 1) return null; - switch (mBuffer.get()) { - case INT: - if (mBuffer.remaining() < 4) return null; + byte type = mBuffer.get(); + switch (type) { + case INT_TYPE: return (Integer) mBuffer.getInt(); - case LONG: - if (mBuffer.remaining() < 8) return null; + case LONG_TYPE: return (Long) mBuffer.getLong(); - case STRING: + case STRING_TYPE: try { - if (mBuffer.remaining() < 4) return null; int length = mBuffer.getInt(); - if (length < 0 || mBuffer.remaining() < length) return null; int start = mBuffer.position(); mBuffer.position(start + length); return new String(mBuffer.array(), start, length, "UTF-8"); } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // UTF-8 is guaranteed. + Log.wtf(TAG, "UTF-8 is not supported", e); + return null; } - case LIST: - if (mBuffer.remaining() < 1) return null; + case LIST_TYPE: int length = mBuffer.get(); - if (length < 0) return null; Object[] array = new Object[length]; - for (int i = 0; i < length; ++i) { - array[i] = decodeObject(); - if (array[i] == null) return null; - } - return new List(array); + for (int i = 0; i < length; ++i) array[i] = decodeObject(); + return array; default: - return null; + throw new IllegalArgumentException("Unknown entry type: " + type); } } } @@ -236,46 +151,36 @@ public class EventLog { // We assume that the native methods deal with any concurrency issues. /** - * Send an event log message. - * @param tag An event identifer + * Record an event log message. + * @param tag The event type tag code * @param value A value to log * @return The number of bytes written */ public static native int writeEvent(int tag, int value); /** - * Send an event log message. - * @param tag An event identifer + * Record an event log message. + * @param tag The event type tag code * @param value A value to log * @return The number of bytes written */ public static native int writeEvent(int tag, long value); /** - * Send an event log message. - * @param tag An event identifer + * Record an event log message. + * @param tag The event type tag code * @param str A value to log * @return The number of bytes written */ public static native int writeEvent(int tag, String str); /** - * Send an event log message. - * @param tag An event identifer - * @param list A {@link List} to log - * @return The number of bytes written - */ - public static native int writeEvent(int tag, List list); - - /** - * Send an event log message. - * @param tag An event identifer + * Record an event log message. + * @param tag The event type tag code * @param list A list of values to log * @return The number of bytes written */ - public static int writeEvent(int tag, Object... list) { - return writeEvent(tag, new List(list)); - } + public static native int writeEvent(int tag, Object... list); /** * Read events from the log, filtered by type. @@ -287,11 +192,65 @@ public class EventLog { throws IOException; /** - * Read events from a file. - * @param path to read from - * @param output container to add events into - * @throws IOException if something goes wrong reading events + * Get the name associated with an event type tag code. + * @param tag code to look up + * @return the name of the tag, or null if no tag has that number */ - public static native void readEvents(String path, Collection<Event> output) - throws IOException; + public static String getTagName(int tag) { + readTagsFile(); + return sTagNames.get(tag); + } + + /** + * Get the event type tag code associated with an event name. + * @param name of event to look up + * @return the tag code, or -1 if no tag has that name + */ + public static int getTagCode(String name) { + readTagsFile(); + Integer code = sTagCodes.get(name); + return code != null ? code : -1; + } + + /** + * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done. + */ + private static synchronized void readTagsFile() { + if (sTagCodes != null && sTagNames != null) return; + + sTagCodes = new HashMap<String, Integer>(); + sTagNames = new HashMap<Integer, String>(); + + Pattern comment = Pattern.compile(COMMENT_PATTERN); + Pattern tag = Pattern.compile(TAG_PATTERN); + BufferedReader reader = null; + String line; + + try { + reader = new BufferedReader(new FileReader(TAGS_FILE), 256); + while ((line = reader.readLine()) != null) { + if (comment.matcher(line).matches()) continue; + + Matcher m = tag.matcher(line); + if (!m.matches()) { + Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line); + continue; + } + + try { + int num = Integer.parseInt(m.group(1)); + String name = m.group(2); + sTagCodes.put(name, num); + sTagNames.put(num, name); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e); + } + } + } catch (IOException e) { + Log.wtf(TAG, "Error reading " + TAGS_FILE, e); + // Leave the maps existing but unpopulated + } finally { + try { if (reader != null) reader.close(); } catch (IOException e) {} + } + } } diff --git a/core/java/android/util/EventLogTags.java b/core/java/android/util/EventLogTags.java index be905e3..075c84e 100644 --- a/core/java/android/util/EventLogTags.java +++ b/core/java/android/util/EventLogTags.java @@ -25,16 +25,14 @@ import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** Parsed representation of /etc/event-log-tags. */ +/** + * to-be-deprecated: This class is no longer functional. + * Use {to-be-link android.util.EventLog} instead. + */ public class EventLogTags { - private final static String TAG = "EventLogTags"; - - private final static String TAGS_FILE = "/etc/event-log-tags"; - public static class Description { public final int mTag; public final String mName; - // TODO: Parse parameter descriptions when anyone has a use for them. Description(int tag, String name) { mTag = tag; @@ -42,49 +40,11 @@ public class EventLogTags { } } - private final static Pattern COMMENT_PATTERN = Pattern.compile( - "^\\s*(#.*)?$"); - - private final static Pattern TAG_PATTERN = Pattern.compile( - "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$"); + public EventLogTags() throws IOException {} - private final HashMap<String, Description> mNameMap = - new HashMap<String, Description>(); - - private final HashMap<Integer, Description> mTagMap = - new HashMap<Integer, Description>(); - - public EventLogTags() throws IOException { - this(new BufferedReader(new FileReader(TAGS_FILE), 256)); - } + public EventLogTags(BufferedReader input) throws IOException {} - public EventLogTags(BufferedReader input) throws IOException { - String line; - while ((line = input.readLine()) != null) { - Matcher m = COMMENT_PATTERN.matcher(line); - if (m.matches()) continue; + public Description get(String name) { return null; } - m = TAG_PATTERN.matcher(line); - if (m.matches()) { - try { - int tag = Integer.parseInt(m.group(1)); - Description d = new Description(tag, m.group(2)); - mNameMap.put(d.mName, d); - mTagMap.put(d.mTag, d); - } catch (NumberFormatException e) { - Log.e(TAG, "Error in event log tags entry: " + line, e); - } - } else { - Log.e(TAG, "Can't parse event log tags entry: " + line); - } - } - } - - public Description get(String name) { - return mNameMap.get(name); - } - - public Description get(int tag) { - return mTagMap.get(tag); - } + public Description get(int tag) { return null; } } diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index e95d0be..7a959a6 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -81,6 +81,13 @@ public final class Log { */ public static final int ASSERT = 7; + /** + * Exception class used to capture a stack trace in {@link #wtf()}. + */ + private static class TerribleFailure extends Exception { + TerribleFailure(String msg, Throwable cause) { super(msg, cause); } + } + private Log() { } @@ -170,24 +177,24 @@ public final class Log { /** * Checks to see whether or not a log for the specified tag is loggable at the specified level. - * + * * The default level of any tag is set to INFO. This means that any level above and including * INFO will be logged. Before you make any calls to a logging method you should check to see * if your tag should be logged. You can change the default level by setting a system property: * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' - * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPRESS will + * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will * turn off all logging for your tag. You can also create a local.prop file that with the * following in it: * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' * and place that in /data/local.prop. - * + * * @param tag The tag to check. * @param level The level to check. * @return Whether or not that this is allowed to be logged. * @throws IllegalArgumentException is thrown if the tag.length() > 23. */ public static native boolean isLoggable(String tag, int level); - + /* * Send a {@link #WARN} log message and log the exception. * @param tag Used to identify the source of a log message. It usually identifies @@ -220,6 +227,46 @@ public final class Log { } /** + * What a Terrible Failure: Report a condition that should never happen. + * The error will always be logged at level ASSERT with the call stack. + * Depending on system configuration, a report may be added to the + * {@link android.os.DropBoxManager} and/or the process may be terminated + * immediately with an error dialog. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + * @pending + */ + public static int wtf(String tag, String msg) { + return wtf(tag, msg, null); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, String)}, with an exception to log. + * @param tag Used to identify the source of a log message. + * @param tr An exception to log. + * @pending + */ + public static int wtf(String tag, Throwable tr) { + return wtf(tag, tr.getMessage(), tr); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, Throwable)}, with a message as well. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + * @param tr An exception to log. May be null. + * @pending + */ + public static int wtf(String tag, String msg, Throwable tr) { + tr = new TerribleFailure(msg, tr); + int bytes = println(ASSERT, tag, getStackTraceString(tr)); + RuntimeInit.wtf(tag, tr); + return bytes; + } + + /** * Handy function to get a loggable stack trace from a Throwable * @param tr An exception to log */ diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 0fc70d5..4f496d7 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -27,7 +27,7 @@ import java.io.IOException; import java.util.TimeZone; import java.util.Date; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; /** * A class containing utility methods related to time zones. diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java index 12d6dd9..8f855cd 100644 --- a/core/java/android/util/XmlPullAttributes.java +++ b/core/java/android/util/XmlPullAttributes.java @@ -19,7 +19,7 @@ package android.util; import org.xmlpull.v1.XmlPullParser; import android.util.AttributeSet; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; /** * Provides an implementation of AttributeSet on top of an XmlPullParser. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4176ed1..5bd45a66a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1494,6 +1494,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @hide */ static final int OPAQUE_MASK = 0x01800000; + + /** + * Indicates a prepressed state; + * the short time between ACTION_DOWN and recognizing + * a 'real' press. Prepressed is used to recognize quick taps + * even when they are shorter than ViewConfiguration.getTapTimeout(). + * + * @hide + */ + private static final int PREPRESSED = 0x02000000; /** * The parent this view is attached to. @@ -1722,6 +1732,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility private int mNextFocusDownId = View.NO_ID; private CheckForLongPress mPendingCheckForLongPress; + private CheckForTap mPendingCheckForTap = null; + private UnsetPressedState mUnsetPressedState; /** @@ -1762,6 +1774,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * Special tree observer used when mAttachInfo is null. */ private ViewTreeObserver mFloatingTreeObserver; + + /** + * Cache the touch slop from the context that created the view. + */ + private int mTouchSlop; // Used for debug only static long sInstanceCount = 0; @@ -1777,6 +1794,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility mResources = context != null ? context.getResources() : null; mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; ++sInstanceCount; + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } /** @@ -3951,7 +3969,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility (event.getRepeatCount() == 0)) { setPressed(true); if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { - postCheckForLongClick(); + postCheckForLongClick(0); } return true; } @@ -4174,7 +4192,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: - if ((mPrivateFlags & PRESSED) != 0) { + boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; + if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; @@ -4196,24 +4215,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility mUnsetPressedState = new UnsetPressedState(); } - if (!post(mUnsetPressedState)) { + if (prepressed) { + mPrivateFlags |= PRESSED; + refreshDrawableState(); + postDelayed(mUnsetPressedState, + ViewConfiguration.getPressedStateDuration()); + } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } + removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: - mPrivateFlags |= PRESSED; - refreshDrawableState(); - if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { - postCheckForLongClick(); + if (mPendingCheckForTap == null) { + mPendingCheckForTap = new CheckForTap(); } + mPrivateFlags |= PREPRESSED; + postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); + removeTapCallback(); break; case MotionEvent.ACTION_MOVE: @@ -4221,25 +4247,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility final int y = (int) event.getY(); // Be lenient about moving outside of buttons - int slop = ViewConfiguration.get(mContext).getScaledTouchSlop(); + int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button + removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { - // Remove any future long press checks + // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } - } else { - // Inside button - if ((mPrivateFlags & PRESSED) == 0) { - // Need to switch from not pressed to pressed - mPrivateFlags |= PRESSED; - refreshDrawableState(); - } } break; } @@ -4257,6 +4277,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility removeCallbacks(mPendingCheckForLongPress); } } + + /** + * Remove the tap detection timer. + */ + private void removeTapCallback() { + if (mPendingCheckForTap != null) { + mPrivateFlags &= ~PREPRESSED; + removeCallbacks(mPendingCheckForTap); + } + } /** * Cancels a pending long press. Your subclass can use this if you @@ -8427,14 +8457,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } } - private void postCheckForLongClick() { + private void postCheckForLongClick(int delayOffset) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount(); - postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout()); + postDelayed(mPendingCheckForLongPress, + ViewConfiguration.getLongPressTimeout() - delayOffset); } private static int[] stateSetUnion(final int[] stateSet1, @@ -8611,6 +8642,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility mOriginalWindowAttachCount = mWindowAttachCount; } } + + private final class CheckForTap implements Runnable { + public void run() { + mPrivateFlags &= ~PREPRESSED; + mPrivateFlags |= PRESSED; + refreshDrawableState(); + if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { + postCheckForLongClick(ViewConfiguration.getTapTimeout()); + } + } + } /** * Interface definition for a callback to be invoked when a key event is diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 993048f..2344c42 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -49,7 +49,7 @@ public class ViewConfiguration { * Defines the duration in milliseconds of the pressed state in child * components. */ - private static final int PRESSED_STATE_DURATION = 85; + private static final int PRESSED_STATE_DURATION = 125; /** * Defines the duration in milliseconds before a press turns into @@ -69,7 +69,7 @@ public class ViewConfiguration { * is a tap or a scroll. If the user does not move within this interval, it is * considered to be a tap. */ - private static final int TAP_TIMEOUT = 100; + private static final int TAP_TIMEOUT = 115; /** * Defines the duration in milliseconds we will wait to see if a touch event diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 9232616..3ebe1c2 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -910,7 +910,7 @@ public class ViewDebug { private static void dump(View root, OutputStream clientStream) throws IOException { BufferedWriter out = null; try { - out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024); + out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024); View view = root.getRootView(); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java index 703a38f..d5e9af4 100644 --- a/core/java/android/view/ViewStub.java +++ b/core/java/android/view/ViewStub.java @@ -207,9 +207,11 @@ public final class ViewStub extends View { } else { throw new IllegalStateException("setVisibility called on un-referenced view"); } - } else if (visibility == VISIBLE || visibility == INVISIBLE) { + } else { super.setVisibility(visibility); - inflate(); + if (visibility == VISIBLE || visibility == INVISIBLE) { + inflate(); + } } } @@ -244,7 +246,7 @@ public final class ViewStub extends View { parent.addView(view, index); } - mInflatedViewRef = new WeakReference(view); + mInflatedViewRef = new WeakReference<View>(view); if (mInflateListener != null) { mInflateListener.onInflate(this, view); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 5d3840a..bbe9c1f 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -662,6 +662,14 @@ public interface WindowManagerPolicy { public boolean finishAnimationLw(); /** + * Return true if it is okay to perform animations for an app transition + * that is about to occur. You may return false for this if, for example, + * the lock screen is currently displayed so the switch should happen + * immediately. + */ + public boolean allowAppAnimationsLw(); + + /** * Called after the screen turns off. * * @param why {@link #OFF_BECAUSE_OF_USER} or @@ -675,6 +683,11 @@ public interface WindowManagerPolicy { public void screenTurnedOn(); /** + * Return whether the screen is currently on. + */ + public boolean isScreenOn(); + + /** * Perform any initial processing of a low-level input event before the * window manager handles special keys and generates a high-level event * that is dispatched to the application. diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index 13606e7..2aba60b 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -33,13 +33,12 @@ import android.util.Log; public abstract class WindowOrientationListener { private static final String TAG = "WindowOrientationListener"; private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private static final boolean localLOGV = DEBUG || Config.DEBUG; private SensorManager mSensorManager; private boolean mEnabled = false; private int mRate; private Sensor mSensor; - private SensorEventListener mSensorEventListener; - private int mSensorRotation = -1; + private SensorEventListenerImpl mSensorEventListener; /** * Creates a new WindowOrientationListener. @@ -80,7 +79,6 @@ public abstract class WindowOrientationListener { } if (mEnabled == false) { if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled"); - mSensorRotation = -1; mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); mEnabled = true; } @@ -96,23 +94,22 @@ public abstract class WindowOrientationListener { } if (mEnabled == true) { if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled"); - mSensorRotation = -1; mSensorManager.unregisterListener(mSensorEventListener); mEnabled = false; } } public int getCurrentRotation() { - return mSensorRotation; + if (mEnabled) { + return mSensorEventListener.getCurrentRotation(); + } + return -1; } class SensorEventListenerImpl implements SensorEventListener { private static final int _DATA_X = 0; private static final int _DATA_Y = 1; private static final int _DATA_Z = 2; - // Angle around x-axis thats considered almost perfect vertical to hold - // the device - private static final int PIVOT = 20; // Angle around x-asis that's considered almost too vertical. Beyond // this angle will not result in any orientation changes. f phone faces uses, // the device is leaning backward. @@ -121,30 +118,61 @@ public abstract class WindowOrientationListener { // angle will not result in any orientation changes. If phone faces uses, // the device is leaning forward. private static final int PIVOT_LOWER = -10; - // Upper threshold limit for switching from portrait to landscape - private static final int PL_UPPER = 295; - // Lower threshold limit for switching from landscape to portrait - private static final int LP_LOWER = 320; - // Lower threshold limt for switching from portrait to landscape - private static final int PL_LOWER = 270; - // Upper threshold limit for switching from landscape to portrait - private static final int LP_UPPER = 359; - // Minimum angle which is considered landscape - private static final int LANDSCAPE_LOWER = 235; - // Minimum angle which is considered portrait - private static final int PORTRAIT_LOWER = 60; - - // Internal value used for calculating linear variant - private static final float PL_LF_UPPER = - ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT_UPPER-PIVOT)); - private static final float PL_LF_LOWER = - ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT-PIVOT_LOWER)); - // Internal value used for calculating linear variant - private static final float LP_LF_UPPER = - ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT_UPPER-PIVOT)); - private static final float LP_LF_LOWER = - ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT-PIVOT_LOWER)); + static final int ROTATION_0 = 0; + static final int ROTATION_90 = 1; + static final int ROTATION_180 = 2; + static final int ROTATION_270 = 3; + int mRotation = ROTATION_0; + + // Threshold values defined for device rotation positions + // follow order ROTATION_0 .. ROTATION_270 + final int THRESHOLDS[][][] = new int[][][] { + {{60, 135}, {135, 225}, {225, 300}}, + {{0, 45}, {45, 135}, {135, 210}, {330, 360}}, + {{0, 45}, {45, 120}, {240, 315}, {315, 360}}, + {{0, 30}, {150, 225}, {225, 315}, {315, 360}} + }; + + // Transform rotation ranges based on THRESHOLDS. This + // has to be in step with THESHOLDS + final int ROTATE_TO[][] = new int[][] { + {ROTATION_270, ROTATION_180, ROTATION_90}, + {ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0}, + {ROTATION_0, ROTATION_270, ROTATION_90, ROTATION_0}, + {ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0} + }; + + // Mapping into actual Surface rotation values + final int TRANSFORM_ROTATIONS[] = new int[]{Surface.ROTATION_0, + Surface.ROTATION_90, Surface.ROTATION_180, Surface.ROTATION_270}; + + int getCurrentRotation() { + return TRANSFORM_ROTATIONS[mRotation]; + } + private void calculateNewRotation(int orientation, int zyangle) { + if (localLOGV) Log.i(TAG, orientation + ", " + zyangle + ", " + mRotation); + int rangeArr[][] = THRESHOLDS[mRotation]; + int row = -1; + for (int i = 0; i < rangeArr.length; i++) { + if ((orientation >= rangeArr[i][0]) && (orientation < rangeArr[i][1])) { + row = i; + break; + } + } + if (row != -1) { + // Find new rotation based on current rotation value. + // This also takes care of irregular rotations as well. + int rotation = ROTATE_TO[mRotation][row]; + if (localLOGV) Log.i(TAG, " new rotation = " + rotation); + if (rotation != mRotation) { + mRotation = rotation; + // Trigger orientation change + onOrientationChanged(TRANSFORM_ROTATIONS[rotation]); + } + } + } + public void onSensorChanged(SensorEvent event) { float[] values = event.values; float X = values[_DATA_X]; @@ -153,53 +181,19 @@ public abstract class WindowOrientationListener { float OneEightyOverPi = 57.29577957855f; float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z); float zyangle = (float)Math.asin(Z/gravity)*OneEightyOverPi; - int rotation = -1; if ((zyangle <= PIVOT_UPPER) && (zyangle >= PIVOT_LOWER)) { // Check orientation only if the phone is flat enough // Don't trust the angle if the magnitude is small compared to the y value float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi; - int orientation = 90 - (int)Math.round(angle); + int orientation = 90 - Math.round(angle); // normalize to 0 - 359 range while (orientation >= 360) { orientation -= 360; - } + } while (orientation < 0) { orientation += 360; } - // Orientation values between LANDSCAPE_LOWER and PL_LOWER - // are considered landscape. - // Ignore orientation values between 0 and LANDSCAPE_LOWER - // For orientation values between LP_UPPER and PL_LOWER, - // the threshold gets set linearly around PIVOT. - if ((orientation >= PL_LOWER) && (orientation <= LP_UPPER)) { - float threshold; - float delta = zyangle - PIVOT; - if (mSensorRotation == Surface.ROTATION_90) { - if (delta < 0) { - // Delta is negative - threshold = LP_LOWER - (LP_LF_LOWER * delta); - } else { - threshold = LP_LOWER + (LP_LF_UPPER * delta); - } - rotation = (orientation >= threshold) ? Surface.ROTATION_0 : Surface.ROTATION_90; - } else { - if (delta < 0) { - // Delta is negative - threshold = PL_UPPER+(PL_LF_LOWER * delta); - } else { - threshold = PL_UPPER-(PL_LF_UPPER * delta); - } - rotation = (orientation <= threshold) ? Surface.ROTATION_90: Surface.ROTATION_0; - } - } else if ((orientation >= LANDSCAPE_LOWER) && (orientation < LP_LOWER)) { - rotation = Surface.ROTATION_90; - } else if ((orientation >= PL_UPPER) || (orientation <= PORTRAIT_LOWER)) { - rotation = Surface.ROTATION_0; - } - if ((rotation != -1) && (rotation != mSensorRotation)) { - mSensorRotation = rotation; - onOrientationChanged(mSensorRotation); - } + calculateNewRotation(orientation, Math.round(zyangle)); } } diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index b36fa8d..1337bed 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -339,6 +339,7 @@ class BrowserFrame extends Handler { // loadType is not used yet if (isMainFrame) { mCommitted = true; + mWebViewCore.getWebView().mViewManager.postResetStateAll(); } } diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index c4e26bc..22dca3a 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -56,6 +56,9 @@ public final class CacheManager { private static long CACHE_THRESHOLD = 6 * 1024 * 1024; private static long CACHE_TRIM_AMOUNT = 2 * 1024 * 1024; + // Limit the maximum cache file size to half of the normal capacity + static long CACHE_MAX_SIZE = (CACHE_THRESHOLD - CACHE_TRIM_AMOUNT) / 2; + private static boolean mDisabled; // Reference count the enable/disable transaction @@ -448,7 +451,6 @@ public final class CacheManager { return; } - cacheRet.contentLength = cacheRet.outFile.length(); boolean redirect = checkCacheRedirect(cacheRet.httpStatusCode); if (redirect) { // location is in database, no need to keep the file @@ -470,6 +472,15 @@ public final class CacheManager { } } + static boolean cleanupCacheFile(CacheResult cacheRet) { + try { + cacheRet.outStream.close(); + } catch (IOException e) { + return false; + } + return cacheRet.outFile.delete(); + } + /** * remove all cache files * @@ -644,6 +655,9 @@ public final class CacheManager { private static CacheResult parseHeaders(int statusCode, Headers headers, String mimeType) { + // if the contentLength is already larger than CACHE_MAX_SIZE, skip it + if (headers.getContentLength() > CACHE_MAX_SIZE) return null; + // TODO: if authenticated or secure, return null CacheResult ret = new CacheResult(); ret.httpStatusCode = statusCode; diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 7eb42f2..fb369d3 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -108,8 +108,6 @@ class CallbackProxy extends Handler { private static final int RECEIVED_TOUCH_ICON_URL = 132; private static final int GET_VISITED_HISTORY = 133; private static final int OPEN_FILE_CHOOSER = 134; - private static final int SHOW_CUSTOM_VIEW = 135; - private static final int HIDE_CUSTOM_VIEW = 136; // Message triggered by the client to resume execution private static final int NOTIFY = 200; @@ -681,23 +679,6 @@ class CallbackProxy extends Handler { mWebChromeClient.openFileChooser((UploadFile) msg.obj); } break; - - case SHOW_CUSTOM_VIEW: - if (mWebChromeClient != null) { - HashMap<String, Object> map = - (HashMap<String, Object>) msg.obj; - View view = (View) map.get("view"); - WebChromeClient.CustomViewCallback callback = - (WebChromeClient.CustomViewCallback) map.get("callback"); - mWebChromeClient.onShowCustomView(view, callback); - } - break; - - case HIDE_CUSTOM_VIEW: - if (mWebChromeClient != null) { - mWebChromeClient.onHideCustomView(); - } - break; } } @@ -1404,24 +1385,4 @@ class CallbackProxy extends Handler { } return uploadFile.getResult(); } - - /* package */ void showCustomView(View view, WebChromeClient.CustomViewCallback callback) { - if (mWebChromeClient == null) { - return; - } - Message msg = obtainMessage(SHOW_CUSTOM_VIEW); - HashMap<String, Object> map = new HashMap<String, Object>(); - map.put("view", view); - map.put("callback", callback); - msg.obj = map; - sendMessage(msg); - } - - /* package */ void hideCustomView() { - if (mWebChromeClient == null) { - return; - } - Message msg = obtainMessage(HIDE_CUSTOM_VIEW); - sendMessage(msg); - } } diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java index 8e25395..dca52f6 100644 --- a/core/java/android/webkit/DebugFlags.java +++ b/core/java/android/webkit/DebugFlags.java @@ -32,6 +32,8 @@ class DebugFlags { public static final boolean CALLBACK_PROXY = false; public static final boolean COOKIE_MANAGER = false; public static final boolean COOKIE_SYNC_MANAGER = false; + public static final boolean DRAG_TRACKER = false; + public static final String DRAG_TRACKER_LOGTAG = "skia"; public static final boolean FRAME_LOADER = false; public static final boolean J_WEB_CORE_JAVA_BRIDGE = false;// HIGHLY VERBOSE public static final boolean LOAD_LISTENER = false; diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index c2b9f20..cdc6608 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -936,8 +936,11 @@ class LoadListener extends Handler implements EventHandler { void downloadFile() { // Setting the Cache Result to null ensures that this // content is not added to the cache - mCacheResult = null; - + if (mCacheResult != null) { + CacheManager.cleanupCacheFile(mCacheResult); + mCacheResult = null; + } + // Inform the client that they should download a file mBrowserFrame.getCallbackProxy().onDownloadStart(url(), mBrowserFrame.getUserAgentString(), @@ -1096,10 +1099,18 @@ class LoadListener extends Handler implements EventHandler { if (c.mLength != 0) { if (mCacheResult != null) { - try { - mCacheResult.outStream.write(c.mArray, 0, c.mLength); - } catch (IOException e) { + mCacheResult.contentLength += c.mLength; + if (mCacheResult.contentLength > CacheManager.CACHE_MAX_SIZE) { + CacheManager.cleanupCacheFile(mCacheResult); mCacheResult = null; + } else { + try { + mCacheResult.outStream + .write(c.mArray, 0, c.mLength); + } catch (IOException e) { + CacheManager.cleanupCacheFile(mCacheResult); + mCacheResult = null; + } } } nativeAddData(c.mArray, c.mLength); @@ -1117,6 +1128,8 @@ class LoadListener extends Handler implements EventHandler { if (mCacheResult != null) { if (getErrorID() == OK) { CacheManager.saveCacheFile(mUrl, mPostIdentifier, mCacheResult); + } else { + CacheManager.cleanupCacheFile(mCacheResult); } // we need to reset mCacheResult to be null @@ -1181,7 +1194,10 @@ class LoadListener extends Handler implements EventHandler { mRequestHandle = null; } - mCacheResult = null; + if (mCacheResult != null) { + CacheManager.cleanupCacheFile(mCacheResult); + mCacheResult = null; + } mCancelled = true; clearNativeLoader(); @@ -1246,6 +1262,8 @@ class LoadListener extends Handler implements EventHandler { if (getErrorID() == OK) { CacheManager.saveCacheFile(mUrl, mPostIdentifier, mCacheResult); + } else { + CacheManager.cleanupCacheFile(mCacheResult); } mCacheResult = null; } diff --git a/core/java/android/webkit/PluginFullScreenHolder.java b/core/java/android/webkit/PluginFullScreenHolder.java new file mode 100644 index 0000000..6a0b145 --- /dev/null +++ b/core/java/android/webkit/PluginFullScreenHolder.java @@ -0,0 +1,120 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package android.webkit; + +import android.app.Dialog; +import android.graphics.Rect; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; + +class PluginFullScreenHolder extends Dialog { + + private static final String LOGTAG = "FullScreenHolder"; + + private final WebView mWebView; + private final int mNpp; + private int mX; + private int mY; + private int mWidth; + private int mHeight; + + PluginFullScreenHolder(WebView webView, int npp) { + super(webView.getContext(), android.R.style.Theme_NoTitleBar_Fullscreen); + mWebView = webView; + mNpp = npp; + } + + Rect getBound() { + return new Rect(mX, mY, mWidth, mHeight); + } + + /* + * x, y, width, height are in the caller's view coordinate system. (x, y) is + * relative to the top left corner of the caller's view. + */ + void updateBound(int x, int y, int width, int height) { + mX = x; + mY = y; + mWidth = width; + mHeight = height; + } + + @Override + public void onBackPressed() { + mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN) + .sendToTarget(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (event.isSystem()) { + return super.onKeyDown(keyCode, event); + } + mWebView.onKeyDown(keyCode, event); + // always return true as we are the handler + return true; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (event.isSystem()) { + return super.onKeyUp(keyCode, event); + } + mWebView.onKeyUp(keyCode, event); + // always return true as we are the handler + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final float x = event.getX(); + final float y = event.getY(); + // TODO: find a way to know when the dialog size changed so that we can + // cache the ratio + final View decorView = getWindow().getDecorView(); + event.setLocation(mX + x * mWidth / decorView.getWidth(), + mY + y * mHeight / decorView.getHeight()); + mWebView.onTouchEvent(event); + // always return true as we are the handler + return true; + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + mWebView.onTrackballEvent(event); + // always return true as we are the handler + return true; + } + + @Override + protected void onStop() { + super.onStop(); + mWebView.getWebViewCore().sendMessage( + WebViewCore.EventHub.HIDE_FULLSCREEN, mNpp, 0); + } + +} diff --git a/core/java/android/webkit/ViewManager.java b/core/java/android/webkit/ViewManager.java index 6a838c3..75db0a0 100644 --- a/core/java/android/webkit/ViewManager.java +++ b/core/java/android/webkit/ViewManager.java @@ -16,7 +16,6 @@ package android.webkit; -import android.content.Context; import android.view.View; import android.widget.AbsoluteLayout; @@ -26,6 +25,7 @@ class ViewManager { private final WebView mWebView; private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>(); private boolean mHidden; + private boolean mReadyToDraw; class ChildView { int x; @@ -70,6 +70,9 @@ class ViewManager { void attachViewOnUIThread(AbsoluteLayout.LayoutParams lp) { mWebView.addView(mView, lp); mChildren.add(this); + if (!mReadyToDraw) { + mView.setVisibility(View.GONE); + } } void removeView() { @@ -154,4 +157,23 @@ class ViewManager { } mHidden = false; } + + void postResetStateAll() { + mWebView.mPrivateHandler.post(new Runnable() { + public void run() { + mReadyToDraw = false; + } + }); + } + + void postReadyToDrawAll() { + mWebView.mPrivateHandler.post(new Runnable() { + public void run() { + mReadyToDraw = true; + for (ChildView v : mChildren) { + v.mView.setVisibility(View.VISIBLE); + } + } + }); + } } diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 92da456..b6891b1 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -84,12 +84,6 @@ import java.util.ArrayList; // True if the most recent drag event has caused either the TextView to // scroll or the web page to scroll. Gets reset after a touch down. private boolean mScrolled; - // Gets set to true any time the WebTextView has focus, but the navigation - // cache does not yet know that the focus has been changed. This happens - // if the user presses "Next", if the user moves the cursor to a textfield - // and starts typing or clicks the trackball/center key, and when the user - // touches a textfield. - boolean mOkayForFocusNotToMatch; // Whether or not a selection change was generated from webkit. If it was, // we do not need to pass the selection back to webkit. private boolean mFromWebKit; @@ -151,22 +145,6 @@ import java.util.ArrayList; break; } - if (down) { - if (mOkayForFocusNotToMatch) { - if (mWebView.nativeFocusNodePointer() == mNodePointer) { - mOkayForFocusNotToMatch = false; - } - } else if (mWebView.nativeFocusNodePointer() != mNodePointer - && !isArrowKey) { - mWebView.nativeClearCursor(); - // Do not call remove() here, which hides the soft keyboard. If - // the soft keyboard is being displayed, the user will still want - // it there. - mWebView.removeView(this); - mWebView.requestFocus(); - return mWebView.dispatchKeyEvent(event); - } - } Spannable text = (Spannable) getText(); int oldLength = text.length(); // Normally the delete key's dom events are sent via onTextChanged. @@ -324,7 +302,6 @@ import java.util.ArrayList; // focus, set the focus controller back to inactive mWebView.setFocusControllerInactive(); mWebView.nativeMoveCursorToNextTextInput(); - mOkayForFocusNotToMatch = true; // Preemptively rebuild the WebTextView, so that the action will // be set properly. mWebView.rebuildWebTextView(); @@ -865,7 +842,10 @@ import java.util.ArrayList; default: break; } + setHint(null); if (single) { + mWebView.requestLabel(mWebView.nativeFocusCandidateFramePointer(), + mNodePointer); maxLength = mWebView.nativeFocusCandidateMaxLength(); if (type != 2 /* PASSWORD */) { String name = mWebView.nativeFocusCandidateName(); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 7d49cb6..a6a48cb 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -25,6 +25,7 @@ import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Point; import android.graphics.Rect; @@ -207,6 +208,9 @@ public class WebView extends AbsoluteLayout static private final boolean AUTO_REDRAW_HACK = false; // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK private boolean mAutoRedraw; + private int mRootLayer; // C++ pointer to the root layer + private boolean mLayersHaveAnimations; + private EvaluateLayersAnimations mEvaluateThread; static final String LOGTAG = "webview"; @@ -301,6 +305,9 @@ public class WebView extends AbsoluteLayout // Used by WebViewCore to create child views. /* package */ final ViewManager mViewManager; + // Used to display in full screen mode + PluginFullScreenHolder mFullScreenHolder; + /** * Position of the last touch event. */ @@ -479,7 +486,7 @@ public class WebView extends AbsoluteLayout static final int MOVE_OUT_OF_PLUGIN = 19; static final int CLEAR_TEXT_ENTRY = 20; static final int UPDATE_TEXT_SELECTION_MSG_ID = 21; - static final int UPDATE_CLIPBOARD = 22; + static final int LONG_PRESS_CENTER = 23; static final int PREVENT_TOUCH_ID = 24; static final int WEBCORE_NEED_TOUCH_EVENTS = 25; @@ -487,6 +494,12 @@ public class WebView extends AbsoluteLayout static final int INVAL_RECT_MSG_ID = 26; static final int REQUEST_KEYBOARD = 27; static final int DO_MOTION_UP = 28; + static final int SHOW_FULLSCREEN = 29; + static final int HIDE_FULLSCREEN = 30; + static final int DOM_FOCUS_CHANGED = 31; + static final int IMMEDIATE_REPAINT_MSG_ID = 32; + static final int SET_ROOT_LAYER_MSG_ID = 33; + static final int RETURN_LABEL = 34; static final String[] HandlerDebugString = { "REMEMBER_PASSWORD", // = 1; @@ -510,13 +523,19 @@ public class WebView extends AbsoluteLayout "MOVE_OUT_OF_PLUGIN", // = 19; "CLEAR_TEXT_ENTRY", // = 20; "UPDATE_TEXT_SELECTION_MSG_ID", // = 21; - "UPDATE_CLIPBOARD", // = 22; + "22", // = 22; "LONG_PRESS_CENTER", // = 23; "PREVENT_TOUCH_ID", // = 24; "WEBCORE_NEED_TOUCH_EVENTS", // = 25; "INVAL_RECT_MSG_ID", // = 26; "REQUEST_KEYBOARD", // = 27; - "DO_MOTION_UP" // = 28; + "DO_MOTION_UP", // = 28; + "SHOW_FULLSCREEN", // = 29; + "HIDE_FULLSCREEN", // = 30; + "DOM_FOCUS_CHANGED", // = 31; + "IMMEDIATE_REPAINT_MSG_ID", // = 32; + "SET_ROOT_LAYER_MSG_ID", // = 33; + "RETURN_LABEL" // = 34; }; // If the site doesn't use the viewport meta tag to specify the viewport, @@ -765,12 +784,11 @@ public class WebView extends AbsoluteLayout init(); mCallbackProxy = new CallbackProxy(context, this); + mViewManager = new ViewManager(this); mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); mDatabase = WebViewDatabase.getInstance(context); mScroller = new Scroller(context); - mViewManager = new ViewManager(this); - mZoomButtonsController = new ZoomButtonsController(this); mZoomButtonsController.setOnZoomListener(mZoomListener); // ZoomButtonsController positions the buttons at the bottom, but in @@ -1719,6 +1737,13 @@ public class WebView extends AbsoluteLayout return result; } + // Called by JNI when the DOM has changed the focus. Clear the focus so + // that new keys will go to the newly focused field + private void domChangedFocus() { + if (inEditingMode()) { + mPrivateHandler.obtainMessage(DOM_FOCUS_CHANGED).sendToTarget(); + } + } /** * Request the href of an anchor element due to getFocusNodePath returning * "href." If hrefMsg is null, this method returns immediately and does not @@ -2860,16 +2885,7 @@ public class WebView extends AbsoluteLayout return super.drawChild(canvas, child, drawingTime); } - @Override - protected void onDraw(Canvas canvas) { - // if mNativeClass is 0, the WebView has been destroyed. Do nothing. - if (mNativeClass == 0) { - return; - } - int saveCount = canvas.save(); - if (mTitleBar != null) { - canvas.translate(0, (int) mTitleBar.getHeight()); - } + private void drawContent(Canvas canvas) { // Update the buttons in the picture, so when we draw the picture // to the screen, they are in the correct state. // Tell the native side if user is a) touching the screen, @@ -2879,9 +2895,25 @@ public class WebView extends AbsoluteLayout // If mNativeClass is 0, we should not reach here, so we do not // need to check it again. nativeRecordButtons(hasFocus() && hasWindowFocus(), - mTouchMode == TOUCH_SHORTPRESS_START_MODE - || mTrackballDown || mGotCenterDown, false); + mTouchMode == TOUCH_SHORTPRESS_START_MODE + || mTrackballDown || mGotCenterDown, false); drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); + } + + @Override + protected void onDraw(Canvas canvas) { + // if mNativeClass is 0, the WebView has been destroyed. Do nothing. + if (mNativeClass == 0) { + return; + } + + int saveCount = canvas.save(); + if (mTitleBar != null) { + canvas.translate(0, (int) mTitleBar.getHeight()); + } + if (mDragTrackerHandler == null || !mDragTrackerHandler.draw(canvas)) { + drawContent(canvas); + } canvas.restoreToCount(saveCount); // Now draw the shadow. @@ -2896,6 +2928,7 @@ public class WebView extends AbsoluteLayout if (AUTO_REDRAW_HACK && mAutoRedraw) { invalidate(); } + mWebViewCore.signalRepaintDone(); } @Override @@ -2963,11 +2996,20 @@ public class WebView extends AbsoluteLayout } } + private void drawLayers(Canvas canvas) { + if (mRootLayer != 0) { + float scrollY = Math.max(mScrollY - getTitleHeight(), 0); + nativeDrawLayers(mRootLayer, mScrollX, scrollY, + mActualScale, canvas); + } + } + private void drawCoreAndCursorRing(Canvas canvas, int color, boolean drawCursorRing) { if (mDrawHistory) { canvas.scale(mActualScale, mActualScale); canvas.drawPicture(mHistoryPicture); + drawLayers(canvas); return; } @@ -3046,6 +3088,8 @@ public class WebView extends AbsoluteLayout mWebViewCore.drawContentPicture(canvas, color, animateZoom, animateScroll); + drawLayers(canvas); + if (mNativeClass == 0) return; if (mShiftIsPressed && !animateZoom) { if (mTouchSelection || mExtendSelection) { @@ -3177,16 +3221,6 @@ public class WebView extends AbsoluteLayout imm.hideSoftInputFromWindow(this.getWindowToken(), 0); } - /** - * Only for calling from JNI. Allows a click on an unfocused textfield to - * put the textfield in focus. - */ - private void setOkayNotToMatch() { - if (inEditingMode()) { - mWebTextView.mOkayForFocusNotToMatch = true; - } - } - /* * This method checks the current focus and cursor and potentially rebuilds * mWebTextView to have the appropriate properties, such as password, @@ -3276,6 +3310,51 @@ public class WebView extends AbsoluteLayout } } + /** + * Pass a message to find out the <label> associated with the <input> + * identified by nodePointer + * @param framePointer Pointer to the frame containing the <input> node + * @param nodePointer Pointer to the node for which a <label> is desired. + */ + /* package */ void requestLabel(int framePointer, int nodePointer) { + mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer, + nodePointer); + } + + /* + * This class runs the layers animations in their own thread, + * so that we do not slow down the UI. + */ + private class EvaluateLayersAnimations extends Thread { + boolean mRunning = true; + // delay corresponds to 40fps, no need to go faster. + int mDelay = 25; // in ms + public void run() { + while (mRunning) { + if (mLayersHaveAnimations && mRootLayer != 0) { + // updates is a C++ pointer to a Vector of AnimationValues + int updates = nativeEvaluateLayersAnimations(mRootLayer); + if (updates == 0) { + mRunning = false; + } + Message.obtain(mPrivateHandler, + WebView.IMMEDIATE_REPAINT_MSG_ID, + updates, 0).sendToTarget(); + } else { + mRunning = false; + } + try { + Thread.currentThread().sleep(mDelay); + } catch (InterruptedException e) { + mRunning = false; + } + } + } + public void cancel() { + mRunning = false; + } + } + /* * This class requests an Adapter for the WebTextView which shows past * entries stored in the database. It is a Runnable so that it can be done @@ -3448,7 +3527,6 @@ public class WebView extends AbsoluteLayout // Now we need to pass the event to it if (inEditingMode()) { mWebTextView.setDefaultSelection(); - mWebTextView.mOkayForFocusNotToMatch = true; return mWebTextView.dispatchKeyEvent(event); } } else if (nativeHasFocusNode()) { @@ -3545,7 +3623,6 @@ public class WebView extends AbsoluteLayout centerKeyPressOnTextField(); if (inEditingMode()) { mWebTextView.setDefaultSelection(); - mWebTextView.mOkayForFocusNotToMatch = true; } return true; } @@ -3597,14 +3674,22 @@ public class WebView extends AbsoluteLayout private boolean commitCopy() { boolean copiedSomething = false; if (mExtendSelection) { - // copy region so core operates on copy without touching orig. - Region selection = new Region(nativeGetSelection()); - if (selection.isEmpty() == false) { + String selection = nativeGetSelection(); + if (selection != "") { + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "commitCopy \"" + selection + "\""); + } Toast.makeText(mContext , com.android.internal.R.string.text_copied , Toast.LENGTH_SHORT).show(); - mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection); copiedSomething = true; + try { + IClipboard clip = IClipboard.Stub.asInterface( + ServiceManager.getService("clipboard")); + clip.setClipboardText(selection); + } catch (android.os.RemoteException e) { + Log.e(LOGTAG, "Clipboard failed", e); + } } mExtendSelection = false; } @@ -3858,6 +3943,166 @@ public class WebView extends AbsoluteLayout private static final float MAX_SLOPE_FOR_DIAG = 1.5f; private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80; + private static int sign(float x) { + return x > 0 ? 1 : (x < 0 ? -1 : 0); + } + + // if the page can scroll <= this value, we won't allow the drag tracker + // to have any effect. + private static final int MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER = 4; + + private class DragTrackerHandler { + private final DragTracker mProxy; + private final float mStartY, mStartX; + private final float mMinDY, mMinDX; + private final float mMaxDY, mMaxDX; + private float mCurrStretchY, mCurrStretchX; + private int mSX, mSY; + + public DragTrackerHandler(float x, float y, DragTracker proxy) { + mProxy = proxy; + + int docBottom = computeVerticalScrollRange() + getTitleHeight(); + int viewTop = getScrollY(); + int viewBottom = viewTop + getHeight(); + + mStartY = y; + mMinDY = -viewTop; + mMaxDY = docBottom - viewBottom; + + if (DebugFlags.DRAG_TRACKER) { + Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " dragtracker y= " + y + + " up/down= " + mMinDY + " " + mMaxDY); + } + + int docRight = computeHorizontalScrollRange(); + int viewLeft = getScrollX(); + int viewRight = viewLeft + getWidth(); + mStartX = x; + mMinDX = -viewLeft; + mMaxDX = docRight - viewRight; + + mProxy.onStartDrag(x, y); + + // ensure we buildBitmap at least once + mSX = -99999; + } + + private float computeStretch(float delta, float min, float max) { + float stretch = 0; + if (max - min > MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER) { + if (delta < min) { + stretch = delta - min; + } else if (delta > max) { + stretch = delta - max; + } + } + return stretch; + } + + public void dragTo(float x, float y) { + float sy = computeStretch(mStartY - y, mMinDY, mMaxDY); + float sx = computeStretch(mStartX - x, mMinDX, mMaxDX); + + if (mCurrStretchX != sx || mCurrStretchY != sy) { + mCurrStretchX = sx; + mCurrStretchY = sy; + if (DebugFlags.DRAG_TRACKER) { + Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "---- stretch " + sx + + " " + sy); + } + if (mProxy.onStretchChange(sx, sy)) { + invalidate(); + } + } + } + + public void stopDrag() { + if (DebugFlags.DRAG_TRACKER) { + Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag"); + } + mProxy.onStopDrag(); + } + + private int hiddenHeightOfTitleBar() { + return getTitleHeight() - getVisibleTitleHeight(); + } + + // need a way to know if 565 or 8888 is the right config for + // capturing the display and giving it to the drag proxy + private Bitmap.Config offscreenBitmapConfig() { + // hard code 565 for now + return Bitmap.Config.RGB_565; + } + + /* If the tracker draws, then this returns true, otherwise it will + return false, and draw nothing. + */ + public boolean draw(Canvas canvas) { + if (mCurrStretchX != 0 || mCurrStretchY != 0) { + int sx = getScrollX(); + int sy = getScrollY() - hiddenHeightOfTitleBar(); + + if (mSX != sx || mSY != sy) { + buildBitmap(sx, sy); + mSX = sx; + mSY = sy; + } + + int count = canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.translate(sx, sy); + mProxy.onDraw(canvas); + canvas.restoreToCount(count); + return true; + } + if (DebugFlags.DRAG_TRACKER) { + Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " -- draw false " + + mCurrStretchX + " " + mCurrStretchY); + } + return false; + } + + private void buildBitmap(int sx, int sy) { + int w = getWidth(); + int h = getViewHeight(); + Bitmap bm = Bitmap.createBitmap(w, h, offscreenBitmapConfig()); + Canvas canvas = new Canvas(bm); + canvas.translate(-sx, -sy); + drawContent(canvas); + + if (DebugFlags.DRAG_TRACKER) { + Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "--- buildBitmap " + sx + + " " + sy + " " + w + " " + h); + } + mProxy.onBitmapChange(bm); + } + } + + /** @hide */ + public static class DragTracker { + public void onStartDrag(float x, float y) {} + public boolean onStretchChange(float sx, float sy) { + // return true to have us inval the view + return false; + } + public void onStopDrag() {} + public void onBitmapChange(Bitmap bm) {} + public void onDraw(Canvas canvas) {} + } + + /** @hide */ + public DragTracker getDragTracker() { + return mDragTracker; + } + + /** @hide */ + public void setDragTracker(DragTracker tracker) { + mDragTracker = tracker; + } + + private DragTracker mDragTracker; + private DragTrackerHandler mDragTrackerHandler; + @Override public boolean onTouchEvent(MotionEvent ev) { if (mNativeClass == 0 || !isClickable() || !isLongClickable()) { @@ -3956,6 +4201,10 @@ public class WebView extends AbsoluteLayout mLastTouchTime = eventTime; mVelocityTracker = VelocityTracker.obtain(); mSnapScrollMode = SNAP_NONE; + if (mDragTracker != null) { + mDragTrackerHandler = new DragTrackerHandler(x, y, + mDragTracker); + } break; } case MotionEvent.ACTION_MOVE: { @@ -3993,6 +4242,11 @@ public class WebView extends AbsoluteLayout || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); } + if (mFullScreenHolder != null) { + // in full screen mode, the WebView can't be panned. + mTouchMode = TOUCH_DONE_MODE; + break; + } // if it starts nearly horizontal or vertical, enforce it int ax = Math.abs(deltaX); @@ -4120,6 +4374,10 @@ public class WebView extends AbsoluteLayout } } + if (mDragTrackerHandler != null) { + mDragTrackerHandler.dragTo(x, y); + } + if (keepScrollBarsVisible) { if (mHeldMotionless != MOTIONLESS_TRUE) { mHeldMotionless = MOTIONLESS_TRUE; @@ -4135,6 +4393,10 @@ public class WebView extends AbsoluteLayout break; } case MotionEvent.ACTION_UP: { + if (mDragTrackerHandler != null) { + mDragTrackerHandler.stopDrag(); + mDragTrackerHandler = null; + } mLastTouchUpTime = eventTime; switch (mTouchMode) { case TOUCH_DOUBLE_TAP_MODE: // double tap @@ -4147,7 +4409,7 @@ public class WebView extends AbsoluteLayout ted.mX = viewToContentX((int) x + mScrollX); ted.mY = viewToContentY((int) y + mScrollY); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); - } else { + } else if (mFullScreenHolder == null) { doDoubleTap(); } break; @@ -4163,8 +4425,9 @@ public class WebView extends AbsoluteLayout if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquare) { Log.w(LOGTAG, "Miss a drag as we are waiting for" + " WebCore's response for touch down."); - if (computeHorizontalScrollExtent() < computeHorizontalScrollRange() - || computeVerticalScrollExtent() < computeVerticalScrollRange()) { + if (mFullScreenHolder == null + && (computeHorizontalScrollExtent() < computeHorizontalScrollRange() + || computeVerticalScrollExtent() < computeVerticalScrollRange())) { // we will not rewrite drag code here, but we // will try fling if it applies. WebViewCore.pauseUpdate(mWebViewCore); @@ -4224,6 +4487,10 @@ public class WebView extends AbsoluteLayout break; } case MotionEvent.ACTION_CANCEL: { + if (mDragTrackerHandler != null) { + mDragTrackerHandler.stopDrag(); + mDragTrackerHandler = null; + } // we also use mVelocityTracker == null to tell us that we are // not "moving around", so we can take the slower/prettier // mode in the drawing code @@ -5115,7 +5382,7 @@ public class WebView extends AbsoluteLayout // exclude INVAL_RECT_MSG_ID since it is frequently output if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) { Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what - > DO_MOTION_UP ? Integer.toString(msg.what) + > RETURN_LABEL ? Integer.toString(msg.what) : HandlerDebugString[msg.what - REMEMBER_PASSWORD]); } if (mWebViewCore == null) { @@ -5146,7 +5413,9 @@ public class WebView extends AbsoluteLayout mPreventDoubleTap = false; } if (mTouchMode == TOUCH_INIT_MODE) { - mTouchMode = TOUCH_SHORTPRESS_START_MODE; + mTouchMode = mFullScreenHolder == null + ? TOUCH_SHORTPRESS_START_MODE + : TOUCH_SHORTPRESS_MODE; updateSelection(); } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { mTouchMode = TOUCH_DONE_MODE; @@ -5164,8 +5433,10 @@ public class WebView extends AbsoluteLayout mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); } else if (mPreventDrag == PREVENT_DRAG_NO) { mTouchMode = TOUCH_DONE_MODE; - performLongClick(); - rebuildWebTextView(); + if (mFullScreenHolder == null) { + performLongClick(); + rebuildWebTextView(); + } } break; } @@ -5209,7 +5480,8 @@ public class WebView extends AbsoluteLayout final Point viewSize = draw.mViewPoint; boolean useWideViewport = settings.getUseWideViewPort(); WebViewCore.RestoreState restoreState = draw.mRestoreState; - if (restoreState != null) { + boolean hasRestoreState = restoreState != null; + if (hasRestoreState) { mInZoomOverview = false; mLastScale = mInitialScaleInPercent > 0 ? mInitialScaleInPercent / 100.0f @@ -5299,6 +5571,9 @@ public class WebView extends AbsoluteLayout if (draw.mFocusSizeChanged && inEditingMode()) { mFocusSizeChanged = true; } + if (hasRestoreState) { + mViewManager.postReadyToDrawAll(); + } break; } case WEBCORE_INITIALIZED_MSG_ID: @@ -5338,6 +5613,20 @@ public class WebView extends AbsoluteLayout tData.mEnd); } break; + case RETURN_LABEL: + if (inEditingMode() + && mWebTextView.isSameTextField(msg.arg1)) { + mWebTextView.setHint((String) msg.obj); + InputMethodManager imm + = InputMethodManager.peekInstance(); + // The hint is propagated to the IME in + // onCreateInputConnection. If the IME is already + // active, restart it so that its hint text is updated. + if (imm != null && imm.isActive(mWebTextView)) { + imm.restartInput(mWebTextView); + } + } + break; case MOVE_OUT_OF_PLUGIN: navHandledKey(msg.arg1, 1, false, 0, true); break; @@ -5363,25 +5652,44 @@ public class WebView extends AbsoluteLayout } break; } + case IMMEDIATE_REPAINT_MSG_ID: { + int updates = msg.arg1; + if (updates != 0) { + // updates is a C++ pointer to a Vector of + // AnimationValues that we apply to the layers. + // The Vector is deallocated in nativeUpdateLayers(). + nativeUpdateLayers(mRootLayer, updates); + } + invalidate(); + break; + } + case SET_ROOT_LAYER_MSG_ID: { + int oldLayer = mRootLayer; + mRootLayer = msg.arg1; + if (oldLayer > 0) { + nativeDestroyLayer(oldLayer); + } + if (mRootLayer == 0) { + mLayersHaveAnimations = false; + } + if (mEvaluateThread != null) { + mEvaluateThread.cancel(); + mEvaluateThread = null; + } + if (nativeLayersHaveAnimations(mRootLayer)) { + mLayersHaveAnimations = true; + mEvaluateThread = new EvaluateLayersAnimations(); + mEvaluateThread.start(); + } + invalidate(); + break; + } case REQUEST_FORM_DATA: AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj; if (mWebTextView.isSameTextField(msg.arg1)) { mWebTextView.setAdapterCustom(adapter); } break; - case UPDATE_CLIPBOARD: - String str = (String) msg.obj; - if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str); - } - try { - IClipboard clip = IClipboard.Stub.asInterface( - ServiceManager.getService("clipboard")); - clip.setClipboardText(str); - } catch (android.os.RemoteException e) { - Log.e(LOGTAG, "Clipboard failed", e); - } - break; case RESUME_WEBCORE_UPDATE: WebViewCore.resumeUpdate(mWebViewCore); break; @@ -5466,6 +5774,79 @@ public class WebView extends AbsoluteLayout doMotionUp(msg.arg1, msg.arg2, (Boolean) msg.obj); break; + case SHOW_FULLSCREEN: + WebViewCore.PluginFullScreenData data + = (WebViewCore.PluginFullScreenData) msg.obj; + if (data.mNpp != 0 && data.mView != null) { + if (mFullScreenHolder != null) { + Log.w(LOGTAG, + "Should not have another full screen."); + mFullScreenHolder.dismiss(); + } + mFullScreenHolder = new PluginFullScreenHolder( + WebView.this, data.mNpp); + mFullScreenHolder.setContentView(data.mView); + mFullScreenHolder.setCancelable(false); + mFullScreenHolder.setCanceledOnTouchOutside(false); + mFullScreenHolder.show(); + } + // move the matching embedded view fully into the view so + // that touch will be valid instead of rejected due to out + // of the visible bounds + // TODO: do we need to preserve the original position and + // scale so that we can revert it when leaving the full + // screen mode? + int x = contentToViewX(data.mDocX); + int y = contentToViewY(data.mDocY); + int width = contentToViewDimension(data.mDocWidth); + int height = contentToViewDimension(data.mDocHeight); + int viewWidth = getViewWidth(); + int viewHeight = getViewHeight(); + int newX = mScrollX; + int newY = mScrollY; + if (x < mScrollX) { + newX = x + (width > viewWidth + ? (width - viewWidth) / 2 : 0); + } else if (x + width > mScrollX + viewWidth) { + newX = x + width - viewWidth - (width > viewWidth + ? (width - viewWidth) / 2 : 0); + } + if (y < mScrollY) { + newY = y + (height > viewHeight + ? (height - viewHeight) / 2 : 0); + } else if (y + height > mScrollY + viewHeight) { + newY = y + height - viewHeight - (height > viewHeight + ? (height - viewHeight) / 2 : 0); + } + scrollTo(newX, newY); + if (width > viewWidth || height > viewHeight) { + mZoomCenterX = viewWidth * .5f; + mZoomCenterY = viewHeight * .5f; + setNewZoomScale(mActualScale + / Math.max((float) width / viewWidth, + (float) height / viewHeight), false); + } + // Now update the bound + mFullScreenHolder.updateBound(contentToViewX(data.mDocX) + - mScrollX, contentToViewY(data.mDocY) - mScrollY, + contentToViewDimension(data.mDocWidth), + contentToViewDimension(data.mDocHeight)); + break; + + case HIDE_FULLSCREEN: + if (mFullScreenHolder != null) { + mFullScreenHolder.dismiss(); + mFullScreenHolder = null; + } + break; + + case DOM_FOCUS_CHANGED: + if (inEditingMode()) { + nativeClearCursor(); + rebuildWebTextView(); + } + break; + default: super.handleMessage(msg); break; @@ -5977,6 +6358,13 @@ public class WebView extends AbsoluteLayout private native void nativeDebugDump(); private native void nativeDestroy(); private native void nativeDrawCursorRing(Canvas content); + private native void nativeDestroyLayer(int layer); + private native int nativeEvaluateLayersAnimations(int layer); + private native boolean nativeLayersHaveAnimations(int layer); + private native void nativeUpdateLayers(int layer, int updates); + private native void nativeDrawLayers(int layer, + float scrollX, float scrollY, + float scale, Canvas canvas); private native void nativeDrawMatches(Canvas canvas); private native void nativeDrawSelectionPointer(Canvas content, float scale, int x, int y, boolean extendSelection); @@ -5984,7 +6372,7 @@ public class WebView extends AbsoluteLayout private native void nativeDumpDisplayTree(String urlOrNull); private native int nativeFindAll(String findLower, String findUpper); private native void nativeFindNext(boolean forward); - private native int nativeFocusCandidateFramePointer(); + /* package */ native int nativeFocusCandidateFramePointer(); private native boolean nativeFocusCandidateIsPassword(); private native boolean nativeFocusCandidateIsPlugin(); private native boolean nativeFocusCandidateIsRtlText(); @@ -6003,7 +6391,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeFocusIsPlugin(); /* package */ native int nativeFocusNodePointer(); private native Rect nativeGetCursorRingBounds(); - private native Region nativeGetSelection(); + private native String nativeGetSelection(); private native boolean nativeHasCursorNode(); private native boolean nativeHasFocusNode(); private native void nativeHideCursor(); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index e198ee8..724493b 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -41,7 +41,6 @@ import android.view.KeyEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; -import android.webkit.plugin.FullScreenDrawingModel; import android.webkit.plugin.SurfaceDrawingModel; import android.webkit.plugin.WebkitPlugin; @@ -548,8 +547,6 @@ final class WebViewCore { */ private native void nativeSetSelection(int start, int end); - private native String nativeGetSelection(Region sel); - // Register a scheme to be treated as local scheme so that it can access // local asset files for resources private native void nativeRegisterURLSchemeAsLocal(String scheme); @@ -737,7 +734,17 @@ final class WebViewCore { boolean mRemember; } + static class PluginFullScreenData { + View mView; + int mNpp; + int mDocX; + int mDocY; + int mDocWidth; + int mDocHeight; + } + static final String[] HandlerDebugString = { + "REQUEST_LABEL", // 97 "UPDATE_FRAME_CACHE_IF_LOADING", // = 98 "SCROLL_TEXT_INPUT", // = 99 "LOAD_URL", // = 100; @@ -769,7 +776,7 @@ final class WebViewCore { "SET_BACKGROUND_COLOR", // = 126; "SET_MOVE_FOCUS", // = 127 "SAVE_DOCUMENT_STATE", // = 128; - "GET_SELECTION", // = 129; + "129", // = 129; "WEBKIT_DRAW", // = 130; "SYNC_SCROLL", // = 131; "POST_URL", // = 132; @@ -791,6 +798,7 @@ final class WebViewCore { class EventHub { // Message Ids + static final int REQUEST_LABEL = 97; static final int UPDATE_FRAME_CACHE_IF_LOADING = 98; static final int SCROLL_TEXT_INPUT = 99; static final int LOAD_URL = 100; @@ -821,7 +829,7 @@ final class WebViewCore { static final int SET_BACKGROUND_COLOR = 126; static final int SET_MOVE_FOCUS = 127; static final int SAVE_DOCUMENT_STATE = 128; - static final int GET_SELECTION = 129; + static final int WEBKIT_DRAW = 130; static final int SYNC_SCROLL = 131; static final int POST_URL = 132; @@ -870,6 +878,8 @@ final class WebViewCore { static final int POPULATE_VISITED_LINKS = 181; + static final int HIDE_FULLSCREEN = 182; + // private message ids private static final int DESTROY = 200; @@ -901,11 +911,11 @@ final class WebViewCore { @Override public void handleMessage(Message msg) { if (DebugFlags.WEB_VIEW_CORE) { - Log.v(LOGTAG, (msg.what < UPDATE_FRAME_CACHE_IF_LOADING + Log.v(LOGTAG, (msg.what < REQUEST_LABEL || msg.what > VALID_NODE_BOUNDS ? Integer.toString(msg.what) : HandlerDebugString[msg.what - - UPDATE_FRAME_CACHE_IF_LOADING]) + - REQUEST_LABEL]) + " arg1=" + msg.arg1 + " arg2=" + msg.arg2 + " obj=" + msg.obj); } @@ -926,6 +936,19 @@ final class WebViewCore { } break; + case REQUEST_LABEL: + if (mWebView != null) { + int nodePointer = msg.arg2; + String label = nativeRequestLabel(msg.arg1, + nodePointer); + if (label != null && label.length() > 0) { + Message.obtain(mWebView.mPrivateHandler, + WebView.RETURN_LABEL, nodePointer, + 0, label).sendToTarget(); + } + } + break; + case UPDATE_FRAME_CACHE_IF_LOADING: nativeUpdateFrameCacheIfLoading(); break; @@ -1243,13 +1266,6 @@ final class WebViewCore { nativeSetBackgroundColor(msg.arg1); break; - case GET_SELECTION: - String str = nativeGetSelection((Region) msg.obj); - Message.obtain(mWebView.mPrivateHandler - , WebView.UPDATE_CLIPBOARD, str) - .sendToTarget(); - break; - case DUMP_DOMTREE: nativeDumpDomTree(msg.arg1 == 1); break; @@ -1313,6 +1329,10 @@ final class WebViewCore { message); break; } + + case HIDE_FULLSCREEN: + nativeFullScreenPluginHidden(msg.arg1); + break; } } }; @@ -1894,6 +1914,33 @@ final class WebViewCore { } } + private static boolean mRepaintScheduled = false; + + /* + * Called by the WebView thread + */ + /* package */ void signalRepaintDone() { + mRepaintScheduled = false; + } + + // called by JNI + private void sendImmediateRepaint() { + if (mWebView != null && !mRepaintScheduled) { + mRepaintScheduled = true; + Message.obtain(mWebView.mPrivateHandler, + WebView.IMMEDIATE_REPAINT_MSG_ID).sendToTarget(); + } + } + + // called by JNI + private void setRootLayer(int layer) { + if (mWebView != null) { + Message.obtain(mWebView.mPrivateHandler, + WebView.SET_ROOT_LAYER_MSG_ID, + layer, 0).sendToTarget(); + } + } + /* package */ WebView getWebView() { return mWebView; } @@ -1910,7 +1957,14 @@ final class WebViewCore { if (mWebView == null) return; - setupViewport(standardLoad || mRestoredScale > 0); + boolean updateRestoreState = standardLoad || mRestoredScale > 0; + setupViewport(updateRestoreState); + // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will + // be called after the WebView restore the state. If updateRestoreState + // is false, start to draw now as it is ready. + if (!updateRestoreState) { + mWebView.mViewManager.postReadyToDrawAll(); + } // reset the scroll position, the restored offset and scales mWebkitScrollX = mWebkitScrollY = mRestoredX = mRestoredY @@ -2153,7 +2207,7 @@ final class WebViewCore { } private native void nativeUpdateFrameCacheIfLoading(); - + private native String nativeRequestLabel(int framePtr, int nodePtr); /** * Scroll the focused textfield to (xPercent, y) in document space */ @@ -2236,35 +2290,53 @@ final class WebViewCore { // called by JNI. PluginWidget function to launch a full-screen view using a // View object provided by the plugin class. - private void showFullScreenPlugin(WebkitPlugin webkitPlugin, final int npp) { + private void showFullScreenPlugin(WebkitPlugin webkitPlugin, final int npp, + int x, int y, int width, int height) { if (mWebView == null) { return; } - final FullScreenDrawingModel surface = webkitPlugin.getFullScreenSurface(); + final SurfaceDrawingModel surface = webkitPlugin.getFullScreenSurface(); if(surface == null) { - Log.e(LOGTAG, "Attempted to create an full-screen surface with a null drawing model"); + Log.e(LOGTAG, "Attempted to create an full-screen surface with a " + + "null drawing model"); return; } - WebChromeClient.CustomViewCallback callback = new WebChromeClient.CustomViewCallback() { - public void onCustomViewHidden() { - if (surface != null) { - surface.onSurfaceRemoved(); - nativeFullScreenPluginHidden(npp); - } - } - }; - - mCallbackProxy.showCustomView(surface.getSurface(), callback); + PluginFullScreenData data = new PluginFullScreenData(); + data.mView = surface.getSurface(); + data.mNpp = npp; + data.mDocX = x; + data.mDocY = y; + data.mDocWidth = width; + data.mDocHeight = height; + mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN, data) + .sendToTarget(); } + // called by JNI private void hideFullScreenPlugin() { if (mWebView == null) { return; } + mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN) + .sendToTarget(); + } + + // called by JNI + private void updateFullScreenPlugin(int x, int y, int width, int height) { + if (mWebView == null) { + return; + } - mCallbackProxy.hideCustomView(); + PluginFullScreenData data = new PluginFullScreenData(); + data.mDocX = x; + data.mDocY = y; + data.mDocWidth = width; + data.mDocHeight = height; + // null mView and mNpp to indicate it is an update + mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN, data) + .sendToTarget(); } // called by JNI. PluginWidget functions for creating an embedded View for diff --git a/core/java/android/webkit/plugin/FullScreenDrawingModel.java b/core/java/android/webkit/plugin/FullScreenDrawingModel.java deleted file mode 100644 index fe9d197..0000000 --- a/core/java/android/webkit/plugin/FullScreenDrawingModel.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2009, The Android Open Source Project - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package android.webkit.plugin; - -/** - * - * @hide pending API solidification - */ -public interface FullScreenDrawingModel extends SurfaceDrawingModel { - - public void onSurfaceRemoved(); - -} diff --git a/core/java/android/webkit/plugin/WebkitPlugin.java b/core/java/android/webkit/plugin/WebkitPlugin.java index af02cdc..3d13c1c 100644 --- a/core/java/android/webkit/plugin/WebkitPlugin.java +++ b/core/java/android/webkit/plugin/WebkitPlugin.java @@ -30,7 +30,7 @@ package android.webkit.plugin; */ public interface WebkitPlugin { - SurfaceDrawingModel getEmbeddedSurface(); - FullScreenDrawingModel getFullScreenSurface(); + SurfaceDrawingModel getEmbeddedSurface(); + SurfaceDrawingModel getFullScreenSurface(); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 92ff315..e241c77 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -127,11 +127,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te static final int TOUCH_MODE_FLING = 4; /** - * Indicates that the user is currently dragging the fast scroll thumb - */ - static final int TOUCH_MODE_FAST_SCROLL = 5; - - /** * Regular layout - usually an unsolicited layout from the view system */ static final int LAYOUT_NORMAL = 0; @@ -440,6 +435,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private Runnable mClearScrollingCache; private int mMinimumVelocity; private int mMaximumVelocity; + + final boolean[] mIsScrap = new boolean[1]; /** * Interface definition for a callback to be invoked when the list or grid @@ -1239,9 +1236,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * converting an old view or making a new one. * * @param position The position to display + * @param isScrap Array of at least 1 boolean, the first entry will become true if + * the returned view was taken from the scrap heap, false if otherwise. + * * @return A view displaying the data associated with the specified position */ - View obtainView(int position) { + View obtainView(int position, boolean[] isScrap) { + isScrap[0] = false; View scrapView; scrapView = mRecycler.getScrapView(position); @@ -1269,6 +1270,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, position, -1); } + } else { + isScrap[0] = true; } } else { child = mAdapter.getView(position, null, this); @@ -1543,6 +1546,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // Dismiss the popup in case onSaveInstanceState() was not invoked dismissPopup(); + // Detach any view left in the scrap heap + mRecycler.clear(); + final ViewTreeObserver treeObserver = getViewTreeObserver(); if (treeObserver != null) { treeObserver.removeOnTouchModeChangeListener(this); @@ -1636,6 +1642,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mDataChanged) return; if (mAdapter != null && mItemCount > 0 && + mClickMotionPosition != INVALID_POSITION && mClickMotionPosition < mAdapter.getCount() && sameWindow()) { performItemClick(mChild, mClickMotionPosition, getAdapter().getItemId( mClickMotionPosition)); @@ -2969,7 +2976,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te break; case KeyEvent.KEYCODE_SPACE: // Only send spaces once we are filtered - okToSend = mFiltered = true; + okToSend = mFiltered; break; } @@ -3594,12 +3601,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te for (int i = 0; i < count; ++i) { final View victim = activeViews[i]; if (victim != null) { - int whichScrap = ((AbsListView.LayoutParams) - victim.getLayoutParams()).viewType; + int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType; activeViews[i] = null; if (whichScrap == AdapterView.ITEM_VIEW_TYPE_IGNORE) { + removeDetachedView(victim, false); // Do not move views that should be ignored continue; } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index d25530b..d6dd872 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -26,7 +26,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; public abstract class AbsSeekBar extends ProgressBar { - private Drawable mThumb; private int mThumbOffset; @@ -66,8 +65,9 @@ public abstract class AbsSeekBar extends ProgressBar { Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); setThumb(thumb); // will guess mThumbOffset if thumb != null... // ...but allow layout to override this - int thumbOffset = - a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset()); + int thumbOffset = a.getDimensionPixelOffset( + com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset()); + setThumbOffset(thumbOffset); a.recycle(); a = context.obtainStyledAttributes(attrs, @@ -91,7 +91,7 @@ public abstract class AbsSeekBar extends ProgressBar { // Assuming the thumb drawable is symmetric, set the thumb offset // such that the thumb will hang halfway off either edge of the // progress bar. - mThumbOffset = (int)thumb.getIntrinsicWidth() / 2; + mThumbOffset = thumb.getIntrinsicWidth() / 2; } mThumb = thumb; invalidate(); @@ -368,20 +368,21 @@ public abstract class AbsSeekBar extends ProgressBar { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - int progress = getProgress(); - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (progress <= 0) break; - setProgress(progress - mKeyProgressIncrement, true); - onKeyChange(); - return true; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (progress >= getMax()) break; - setProgress(progress + mKeyProgressIncrement, true); - onKeyChange(); - return true; + if (isEnabled()) { + int progress = getProgress(); + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (progress <= 0) break; + setProgress(progress - mKeyProgressIncrement, true); + onKeyChange(); + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (progress >= getMax()) break; + setProgress(progress + mKeyProgressIncrement, true); + onKeyChange(); + return true; + } } return super.onKeyDown(keyCode, event); diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index ce985e3..b455d47 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -1485,8 +1485,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * @return the view for the specified item */ @Override - protected View obtainView(int position) { - View view = super.obtainView(position); + View obtainView(int position, boolean[] isScrap) { + View view = super.obtainView(position, isScrap); if (view instanceof TextView) { ((TextView) view).setHorizontallyScrolling(true); diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index ffe9908..2e91e52 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -931,7 +931,7 @@ public class GridView extends AbsListView { mItemCount = mAdapter == null ? 0 : mAdapter.getCount(); final int count = mItemCount; if (count > 0) { - final View child = obtainView(0); + final View child = obtainView(0, mIsScrap); AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams(); if (p == null) { @@ -1203,7 +1203,7 @@ public class GridView extends AbsListView { View child; if (!mDataChanged) { - // Try to use an exsiting view for this position + // Try to use an existing view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child @@ -1215,10 +1215,10 @@ public class GridView extends AbsListView { // Make a new view for this position, or convert an unused view if // possible - child = obtainView(position); + child = obtainView(position, mIsScrap); // This needs to be positioned and measured - setupChild(child, position, y, flow, childrenLeft, selected, false, where); + setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where); return child; } diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java index d417e40..2fc29bc 100644 --- a/core/java/android/widget/ImageButton.java +++ b/core/java/android/widget/ImageButton.java @@ -44,11 +44,11 @@ import java.util.Map; * <pre> * <?xml version="1.0" encoding="utf-8"?> * <selector xmlns:android="http://schemas.android.com/apk/res/android"> - * <item android:drawable="@drawable/button_normal" /> <!-- default --> * <item android:state_pressed="true" * android:drawable="@drawable/button_pressed" /> <!-- pressed --> * <item android:state_focused="true" * android:drawable="@drawable/button_focused" /> <!-- focused --> + * <item android:drawable="@drawable/button_normal" /> <!-- default --> * </selector></pre> * * <p>Save the XML file in your project {@code res/drawable/} folder and then diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 8f24041..3853359 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -491,8 +491,16 @@ public class ImageView extends View { } } else if (mUri != null) { String scheme = mUri.getScheme(); - if (ContentResolver.SCHEME_CONTENT.equals(scheme) - || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme) + if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { + try { + // Load drawable through Resources, to get the source density information + ContentResolver.OpenResourceIdResult r = + mContext.getContentResolver().getResourceId(mUri); + d = r.r.getDrawable(r.id); + } catch (Exception e) { + Log.w("ImageView", "Unable to open content: " + mUri, e); + } + } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) || ContentResolver.SCHEME_FILE.equals(scheme)) { try { d = Drawable.createFromStream( diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index b574d45..f4008f9 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1033,7 +1033,7 @@ public class ListView extends AbsListView { mItemCount = mAdapter == null ? 0 : mAdapter.getCount(); if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)) { - final View child = obtainView(0); + final View child = obtainView(0, mIsScrap); measureScrapChild(child, 0, widthMeasureSpec); @@ -1142,9 +1142,10 @@ public class ListView extends AbsListView { endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition; final AbsListView.RecycleBin recycleBin = mRecycler; final boolean recyle = recycleOnMeasure(); + final boolean[] isScrap = mIsScrap; for (i = startPosition; i <= endPosition; ++i) { - child = obtainView(i); + child = obtainView(i, isScrap); measureScrapChild(child, i, widthMeasureSpec); @@ -1665,10 +1666,10 @@ public class ListView extends AbsListView { } // Make a new view for this position, or convert an unused view if possible - child = obtainView(position); + child = obtainView(position, mIsScrap); // This needs to be positioned and measured - setupChild(child, position, y, flow, childrenLeft, selected, false); + setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; } @@ -2823,17 +2824,19 @@ public class ListView extends AbsListView { private View addViewAbove(View theView, int position) { int abovePosition = position - 1; - View view = obtainView(abovePosition); + View view = obtainView(abovePosition, mIsScrap); int edgeOfNewChild = theView.getTop() - mDividerHeight; - setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left, false, false); + setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left, + false, mIsScrap[0]); return view; } private View addViewBelow(View theView, int position) { int belowPosition = position + 1; - View view = obtainView(belowPosition); + View view = obtainView(belowPosition, mIsScrap); int edgeOfNewChild = theView.getBottom() + mDividerHeight; - setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left, false, false); + setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left, + false, mIsScrap[0]); return view; } @@ -3080,13 +3083,19 @@ public class ListView extends AbsListView { if (gainFocus && previouslyFocusedRect != null) { previouslyFocusedRect.offset(mScrollX, mScrollY); + final ListAdapter adapter = mAdapter; + final int firstPosition = mFirstPosition; + // Don't cache the result of getChildCount here, it could change in layoutChildren. + if (adapter.getCount() < getChildCount() + firstPosition) { + mLayoutMode = LAYOUT_NORMAL; + layoutChildren(); + } + // figure out which item should be selected based on previously // focused rect Rect otherRect = mTempRect; int minDistance = Integer.MAX_VALUE; final int childCount = getChildCount(); - final int firstPosition = mFirstPosition; - final ListAdapter adapter = mAdapter; for (int i = 0; i < childCount; i++) { // only consider selectable views diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index e19a93d..533c607 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -45,8 +45,7 @@ import java.util.ArrayList; /** * A Layout where the positions of the children can be described in relation to each other or to the - * parent. For the sake of efficiency, the relations between views are evaluated in one pass, so if - * view Y is dependent on the position of view X, make sure the view X comes first in the layout. + * parent. * * <p> * Note that you cannot have a circular dependency between the size of the RelativeLayout and the diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index b847e57..3003580 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -26,6 +26,7 @@ import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -37,7 +38,6 @@ import android.view.ViewGroup; import android.view.LayoutInflater.Filter; import android.view.View.OnClickListener; -import java.lang.Class; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -137,11 +137,21 @@ public class RemoteViews implements Parcelable, Filter { if (target != null && pendingIntent != null) { OnClickListener listener = new OnClickListener() { public void onClick(View v) { - int[] pos = new int[2]; + // Find target view location in screen coordinates and + // fill into PendingIntent before sending. + final float appScale = v.getContext().getResources() + .getCompatibilityInfo().applicationScale; + final int[] pos = new int[2]; v.getLocationOnScreen(pos); - Intent intent = new Intent(); - intent.setSourceBounds(new Rect(pos[0], pos[1], - pos[0]+v.getWidth(), pos[1]+v.getHeight())); + + final Rect rect = new Rect(); + rect.left = (int) (pos[0] * appScale + 0.5f); + rect.top = (int) (pos[1] * appScale + 0.5f); + rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); + rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); + + final Intent intent = new Intent(); + intent.setSourceBounds(rect); try { // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? v.getContext().startIntentSender( @@ -273,6 +283,7 @@ public class RemoteViews implements Parcelable, Filter { static final int CHAR_SEQUENCE = 10; static final int URI = 11; static final int BITMAP = 12; + static final int BUNDLE = 13; int viewId; String methodName; @@ -332,6 +343,9 @@ public class RemoteViews implements Parcelable, Filter { case BITMAP: this.value = Bitmap.CREATOR.createFromParcel(in); break; + case BUNDLE: + this.value = in.readBundle(); + break; default: break; } @@ -384,6 +398,9 @@ public class RemoteViews implements Parcelable, Filter { case BITMAP: ((Bitmap)this.value).writeToParcel(out, flags); break; + case BUNDLE: + out.writeBundle((Bundle) this.value); + break; default: break; } @@ -415,6 +432,8 @@ public class RemoteViews implements Parcelable, Filter { return Uri.class; case BITMAP: return Bitmap.class; + case BUNDLE: + return Bundle.class; default: return null; } @@ -876,6 +895,17 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Call a method taking one Bundle on a view in the layout for this RemoteViews. + * + * @param viewId The id of the view whose text should change + * @param methodName The name of the method to call. + * @param value The value to pass to the method. + */ + public void setBundle(int viewId, String methodName, Bundle value) { + addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); + } + + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. * diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java index 9dd4d15..479965a 100644 --- a/core/java/android/widget/SimpleAdapter.java +++ b/core/java/android/widget/SimpleAdapter.java @@ -25,7 +25,6 @@ import android.net.Uri; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.WeakHashMap; /** * An easy adapter to map static data to views defined in an XML file. You can specify the data @@ -58,7 +57,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { private int mResource; private int mDropDownResource; private LayoutInflater mInflater; - private final WeakHashMap<View, View[]> mHolders = new WeakHashMap<View, View[]>(); private SimpleFilter mFilter; private ArrayList<Map<String, ?>> mUnfilteredData; @@ -121,16 +119,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { View v; if (convertView == null) { v = mInflater.inflate(resource, parent, false); - - final int[] to = mTo; - final int count = to.length; - final View[] holder = new View[count]; - - for (int i = 0; i < count; i++) { - holder[i] = v.findViewById(to[i]); - } - - mHolders.put(v, holder); } else { v = convertView; } @@ -162,13 +150,12 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { } final ViewBinder binder = mViewBinder; - final View[] holder = mHolders.get(view); final String[] from = mFrom; final int[] to = mTo; final int count = to.length; for (int i = 0; i < count; i++) { - final View v = holder[i]; + final View v = view.findViewById(to[i]); if (v != null) { final Object data = dataSet.get(from[i]); String text = data == null ? "" : data.toString(); @@ -187,7 +174,8 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { ((Checkable) v).setChecked((Boolean) data); } else { throw new IllegalStateException(v.getClass().getName() + - " should be bound to a Boolean, not a " + data.getClass()); + " should be bound to a Boolean, not a " + + (data == null ? "<unknown type>" : data.getClass())); } } else if (v instanceof TextView) { // Note: keep the instanceof TextView check at the bottom of these diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java index 436b79b..7d3459e 100644 --- a/core/java/android/widget/SimpleCursorAdapter.java +++ b/core/java/android/widget/SimpleCursorAdapter.java @@ -20,9 +20,6 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.view.View; -import android.view.ViewGroup; - -import java.util.WeakHashMap; /** * An easy adapter to map columns from a cursor to TextViews or ImageViews @@ -66,7 +63,6 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { private CursorToStringConverter mCursorToStringConverter; private ViewBinder mViewBinder; private String[] mOriginalFrom; - private final WeakHashMap<View, View[]> mHolders = new WeakHashMap<View, View[]>(); /** * Constructor. @@ -91,29 +87,6 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { findColumns(from); } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return generateViewHolder(super.newView(context, cursor, parent)); - } - - @Override - public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) { - return generateViewHolder(super.newDropDownView(context, cursor, parent)); - } - - private View generateViewHolder(View v) { - final int[] to = mTo; - final int count = to.length; - final View[] holder = new View[count]; - - for (int i = 0; i < count; i++) { - holder[i] = v.findViewById(to[i]); - } - mHolders.put(v, holder); - - return v; - } - /** * Binds all of the field names passed into the "to" parameter of the * constructor with their corresponding cursor columns as specified in the @@ -140,13 +113,13 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { */ @Override public void bindView(View view, Context context, Cursor cursor) { - final View[] holder = mHolders.get(view); final ViewBinder binder = mViewBinder; final int count = mTo.length; final int[] from = mFrom; + final int[] to = mTo; for (int i = 0; i < count; i++) { - final View v = holder[i]; + final View v = view.findViewById(to[i]); if (v != null) { boolean bound = false; if (binder != null) { diff --git a/core/java/com/android/internal/database/ArrayListCursor.java b/core/java/com/android/internal/database/ArrayListCursor.java deleted file mode 100644 index 2e1d8f1..0000000 --- a/core/java/com/android/internal/database/ArrayListCursor.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.database; - -import android.database.AbstractCursor; -import android.database.CursorWindow; - -import java.lang.System; -import java.util.ArrayList; - -/** - * A convenience class that presents a two-dimensional ArrayList - * as a Cursor. - */ -public class ArrayListCursor extends AbstractCursor { - private String[] mColumnNames; - private ArrayList<Object>[] mRows; - - @SuppressWarnings({"unchecked"}) - public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows) { - int colCount = columnNames.length; - boolean foundID = false; - // Add an _id column if not in columnNames - for (int i = 0; i < colCount; ++i) { - if (columnNames[i].compareToIgnoreCase("_id") == 0) { - mColumnNames = columnNames; - foundID = true; - break; - } - } - - if (!foundID) { - mColumnNames = new String[colCount + 1]; - System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length); - mColumnNames[colCount] = "_id"; - } - - int rowCount = rows.size(); - mRows = new ArrayList[rowCount]; - - for (int i = 0; i < rowCount; ++i) { - mRows[i] = rows.get(i); - if (!foundID) { - mRows[i].add(i); - } - } - } - - @Override - public void fillWindow(int position, CursorWindow window) { - if (position < 0 || position > getCount()) { - return; - } - - window.acquireReference(); - try { - int oldpos = mPos; - mPos = position - 1; - window.clear(); - window.setStartPosition(position); - int columnNum = getColumnCount(); - window.setNumColumns(columnNum); - while (moveToNext() && window.allocRow()) { - for (int i = 0; i < columnNum; i++) { - final Object data = mRows[mPos].get(i); - if (data != null) { - if (data instanceof byte[]) { - byte[] field = (byte[]) data; - if (!window.putBlob(field, mPos, i)) { - window.freeLastRow(); - break; - } - } else { - String field = data.toString(); - if (!window.putString(field, mPos, i)) { - window.freeLastRow(); - break; - } - } - } else { - if (!window.putNull(mPos, i)) { - window.freeLastRow(); - break; - } - } - } - } - - mPos = oldpos; - } catch (IllegalStateException e){ - // simply ignore it - } finally { - window.releaseReference(); - } - } - - @Override - public int getCount() { - return mRows.length; - } - - @Override - public boolean deleteRow() { - return false; - } - - @Override - public String[] getColumnNames() { - return mColumnNames; - } - - @Override - public byte[] getBlob(int columnIndex) { - return (byte[]) mRows[mPos].get(columnIndex); - } - - @Override - public String getString(int columnIndex) { - Object cell = mRows[mPos].get(columnIndex); - return (cell == null) ? null : cell.toString(); - } - - @Override - public short getShort(int columnIndex) { - Number num = (Number) mRows[mPos].get(columnIndex); - return num.shortValue(); - } - - @Override - public int getInt(int columnIndex) { - Number num = (Number) mRows[mPos].get(columnIndex); - return num.intValue(); - } - - @Override - public long getLong(int columnIndex) { - Number num = (Number) mRows[mPos].get(columnIndex); - return num.longValue(); - } - - @Override - public float getFloat(int columnIndex) { - Number num = (Number) mRows[mPos].get(columnIndex); - return num.floatValue(); - } - - @Override - public double getDouble(int columnIndex) { - Number num = (Number) mRows[mPos].get(columnIndex); - return num.doubleValue(); - } - - @Override - public boolean isNull(int columnIndex) { - return mRows[mPos].get(columnIndex) == null; - } -} diff --git a/core/java/com/android/internal/net/DbSSLSessionCache.java b/core/java/com/android/internal/net/DbSSLSessionCache.java deleted file mode 100644 index 842d40b..0000000 --- a/core/java/com/android/internal/net/DbSSLSessionCache.java +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2009 The Android Open Source Project - -package com.android.internal.net; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import org.apache.commons.codec.binary.Base64; -import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; - -import java.util.HashMap; -import java.util.Map; - -import javax.net.ssl.SSLSession; - -/** - * Hook into harmony SSL cache to persist the SSL sessions. - * - * Current implementation is suitable for saving a small number of hosts - - * like google services. It can be extended with expiration and more features - * to support more hosts. - * - * {@hide} - */ -public class DbSSLSessionCache implements SSLClientSessionCache { - private static final String TAG = "DbSSLSessionCache"; - - /** - * Table where sessions are stored. - */ - public static final String SSL_CACHE_TABLE = "ssl_sessions"; - - private static final String SSL_CACHE_ID = "_id"; - - /** - * Key is host:port - port is not optional. - */ - private static final String SSL_CACHE_HOSTPORT = "hostport"; - - /** - * Base64-encoded DER value of the session. - */ - private static final String SSL_CACHE_SESSION = "session"; - - /** - * Time when the record was added - should be close to the time - * of the initial session negotiation. - */ - private static final String SSL_CACHE_TIME_SEC = "time_sec"; - - public static final String DATABASE_NAME = "ssl_sessions.db"; - - public static final int DATABASE_VERSION = 2; - - /** public for testing - */ - public static final int SSL_CACHE_ID_COL = 0; - public static final int SSL_CACHE_HOSTPORT_COL = 1; - public static final int SSL_CACHE_SESSION_COL = 2; - public static final int SSL_CACHE_TIME_SEC_COL = 3; - - public static final int MAX_CACHE_SIZE = 256; - - private final Map<String, byte[]> mExternalCache = - new HashMap<String, byte[]>(); - - - private DatabaseHelper mDatabaseHelper; - - private boolean mNeedsCacheLoad = true; - - public static final String[] PROJECTION = new String[] { - SSL_CACHE_ID, - SSL_CACHE_HOSTPORT, - SSL_CACHE_SESSION, - SSL_CACHE_TIME_SEC - }; - - private static final Map<String,DbSSLSessionCache> sInstances = - new HashMap<String,DbSSLSessionCache>(); - - /** - * Returns a singleton instance of the DbSSLSessionCache that should be used for this - * context's package. - * - * @param context The context that should be used for getting/creating the singleton instance. - * @return The singleton instance for the context's package. - */ - public static synchronized DbSSLSessionCache getInstanceForPackage(Context context) { - String packageName = context.getPackageName(); - if (sInstances.containsKey(packageName)) { - return sInstances.get(packageName); - } - DbSSLSessionCache cache = new DbSSLSessionCache(context); - sInstances.put(packageName, cache); - return cache; - } - - /** - * Create a SslSessionCache instance, using the specified context to - * initialize the database. - * - * This constructor will use the default database - created for the application - * context. - * - * @param activityContext - */ - private DbSSLSessionCache(Context activityContext) { - Context appContext = activityContext.getApplicationContext(); - mDatabaseHelper = new DatabaseHelper(appContext); - } - - /** - * Create a SslSessionCache that uses a specific database. - * - * - * @param database - */ - public DbSSLSessionCache(DatabaseHelper database) { - this.mDatabaseHelper = database; - } - - public void putSessionData(SSLSession session, byte[] der) { - if (mDatabaseHelper == null) { - return; - } - synchronized (this.getClass()) { - SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); - if (mExternalCache.size() == MAX_CACHE_SIZE) { - // remove oldest. - // TODO: check if the new one is in cached already ( i.e. update ). - Cursor byTime = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, - PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC); - if (byTime.moveToFirst()) { - // TODO: can I do byTime.deleteRow() ? - String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL); - db.delete(SSL_CACHE_TABLE, - SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort }); - mExternalCache.remove(hostPort); - } else { - Log.w(TAG, "No rows found"); - // something is wrong, clear it - clear(); - } - } - // Serialize native session to standard DER encoding - long t0 = System.currentTimeMillis(); - - String b64 = new String(Base64.encodeBase64(der)); - String key = session.getPeerHost() + ":" + session.getPeerPort(); - - ContentValues values = new ContentValues(); - values.put(SSL_CACHE_HOSTPORT, key); - values.put(SSL_CACHE_SESSION, b64); - values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000); - - mExternalCache.put(key, der); - - try { - db.insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values); - } catch(SQLException ex) { - // Ignore - nothing we can do to recover, and caller shouldn't - // be affected. - Log.w(TAG, "Ignoring SQL exception when caching session", ex); - } - if (Log.isLoggable(TAG, Log.DEBUG)) { - long t1 = System.currentTimeMillis(); - Log.d(TAG, "New SSL session " + session.getPeerHost() + - " DER len: " + der.length + " " + (t1 - t0)); - } - } - - } - - public byte[] getSessionData(String host, int port) { - // Current (simple) implementation does a single lookup to DB, then saves - // all entries to the cache. - - // This works for google services - i.e. small number of certs. - // If we extend this to all processes - we should hold a separate cache - // or do lookups to DB each time. - if (mDatabaseHelper == null) { - return null; - } - synchronized(this.getClass()) { - if (mNeedsCacheLoad) { - // Don't try to load again, if something is wrong on the first - // request it'll likely be wrong each time. - mNeedsCacheLoad = false; - long t0 = System.currentTimeMillis(); - - Cursor cur = null; - try { - cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, - PROJECTION, null, null, null, null, null); - if (cur.moveToFirst()) { - do { - String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL); - String value = cur.getString(SSL_CACHE_SESSION_COL); - - if (hostPort == null || value == null) { - continue; - } - // TODO: blob support ? - byte[] der = Base64.decodeBase64(value.getBytes()); - mExternalCache.put(hostPort, der); - } while (cur.moveToNext()); - - } - } catch (SQLException ex) { - Log.d(TAG, "Error loading SSL cached entries ", ex); - } finally { - if (cur != null) { - cur.close(); - } - if (Log.isLoggable(TAG, Log.DEBUG)) { - long t1 = System.currentTimeMillis(); - Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms"); - } - } - } - - String key = host + ":" + port; - - return mExternalCache.get(key); - } - } - - /** - * Reset the database and internal state. - * Used for testing or to free space. - */ - public void clear() { - synchronized(this) { - try { - mExternalCache.clear(); - mNeedsCacheLoad = true; - mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE, - null, null); - } catch (SQLException ex) { - Log.d(TAG, "Error removing SSL cached entries ", ex); - // ignore - nothing we can do about it - } - } - } - - public byte[] getSessionData(byte[] id) { - // We support client side only - the cache will do nothing for - // server-side sessions. - return null; - } - - /** Visible for testing. - */ - public static class DatabaseHelper extends SQLiteOpenHelper { - - public DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" + - SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + - SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," + - SSL_CACHE_SESSION + " TEXT," + - SSL_CACHE_TIME_SEC + " INTEGER" + - ");"); - - // No index - we load on startup, index would slow down inserts. - // If we want to scale this to lots of rows - we could use - // index, but then we'll hit DB a bit too often ( including - // negative hits ) - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE ); - onCreate(db); - } - - } - -} diff --git a/core/java/com/android/internal/os/LoggingPrintStream.java b/core/java/com/android/internal/os/LoggingPrintStream.java index b3d6f20..451340b 100644 --- a/core/java/com/android/internal/os/LoggingPrintStream.java +++ b/core/java/com/android/internal/os/LoggingPrintStream.java @@ -16,11 +16,17 @@ package com.android.internal.os; -import java.io.PrintStream; -import java.io.OutputStream; import java.io.IOException; -import java.util.Locale; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; import java.util.Formatter; +import java.util.Locale; /** * A print stream which logs output line by line. @@ -31,6 +37,27 @@ abstract class LoggingPrintStream extends PrintStream { private final StringBuilder builder = new StringBuilder(); + /** + * A buffer that is initialized when raw bytes are first written to this + * stream. It may contain the leading bytes of multi-byte characters. + * Between writes this buffer is always ready to receive data; ie. the + * position is at the first unassigned byte and the limit is the capacity. + */ + private ByteBuffer encodedBytes; + + /** + * A buffer that is initialized when raw bytes are first written to this + * stream. Between writes this buffer is always clear; ie. the position is + * zero and the limit is the capacity. + */ + private CharBuffer decodedChars; + + /** + * Decodes bytes to characters using the system default charset. Initialized + * when raw bytes are first written to this stream. + */ + private CharsetDecoder decoder; + protected LoggingPrintStream() { super(new OutputStream() { public void write(int oneByte) throws IOException { @@ -80,20 +107,48 @@ abstract class LoggingPrintStream extends PrintStream { } } - /* - * We have no idea of how these bytes are encoded, so just ignore them. - */ - - /** Ignored. */ - public void write(int oneByte) {} + public void write(int oneByte) { + write(new byte[] { (byte) oneByte }, 0, 1); + } - /** Ignored. */ @Override - public void write(byte buffer[]) {} + public void write(byte[] buffer) { + write(buffer, 0, buffer.length); + } - /** Ignored. */ @Override - public void write(byte bytes[], int start, int count) {} + public synchronized void write(byte bytes[], int start, int count) { + if (decoder == null) { + encodedBytes = ByteBuffer.allocate(80); + decodedChars = CharBuffer.allocate(80); + decoder = Charset.defaultCharset().newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + } + + int end = start + count; + while (start < end) { + // copy some bytes from the array to the long-lived buffer. This + // way, if we end with a partial character we don't lose it. + int numBytes = Math.min(encodedBytes.remaining(), end - start); + encodedBytes.put(bytes, start, numBytes); + start += numBytes; + + encodedBytes.flip(); + CoderResult coderResult; + do { + // decode bytes from the byte buffer into the char buffer + coderResult = decoder.decode(encodedBytes, decodedChars, false); + + // copy chars from the char buffer into our string builder + decodedChars.flip(); + builder.append(decodedChars); + decodedChars.clear(); + } while (coderResult.isOverflow()); + encodedBytes.compact(); + } + flush(false); + } /** Always returns false. */ @Override diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 2369d25..9e5bdff 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -20,7 +20,7 @@ package com.android.internal.os; import android.content.Context; import android.content.res.XmlResourceParser; -import com.android.internal.util.XmlUtils; +import com.android.common.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/core/java/com/android/internal/os/RecoverySystem.java b/core/java/com/android/internal/os/RecoverySystem.java deleted file mode 100644 index 3aca683..0000000 --- a/core/java/com/android/internal/os/RecoverySystem.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import android.os.FileUtils; -import android.os.Power; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; - -/** - * Utility class for interacting with the Android recovery partition. - * The recovery partition is a small standalone system which can perform - * operations that are difficult while the main system is running, like - * upgrading system software or reformatting the data partition. - * Note that most of these operations must be run as root. - * - * @hide - */ -public class RecoverySystem { - private static final String TAG = "RecoverySystem"; // for logging - - // Used to communicate with recovery. See commands/recovery/recovery.c. - private static File RECOVERY_DIR = new File("/cache/recovery"); - private static File COMMAND_FILE = new File(RECOVERY_DIR, "command"); - private static File LOG_FILE = new File(RECOVERY_DIR, "log"); - - // Length limits for reading files. - private static int LOG_FILE_MAX_LENGTH = 8 * 1024; - - /** - * Reboot into the recovery system to install a system update. - * @param update package to install (must be in /cache or /data). - * @throws IOException if something goes wrong. - */ - public static void rebootAndUpdate(File update) throws IOException { - String path = update.getCanonicalPath(); - if (path.startsWith("/cache/")) { - path = "CACHE:" + path.substring(7); - } else if (path.startsWith("/data/")) { - path = "DATA:" + path.substring(6); - } else { - throw new IllegalArgumentException( - "Must start with /cache or /data: " + path); - } - bootCommand("--update_package=" + path); - } - - /** - * Reboot into the recovery system to wipe the /data partition. - * @param extras to add to the RECOVERY_COMPLETED intent after rebooting. - * @throws IOException if something goes wrong. - */ - public static void rebootAndWipe() throws IOException { - bootCommand("--wipe_data"); - } - - /** - * Reboot into the recovery system to wipe the /data partition and toggle - * Encrypted File Systems on/off. - * @param extras to add to the RECOVERY_COMPLETED intent after rebooting. - * @throws IOException if something goes wrong. - * @hide - */ - public static void rebootAndToggleEFS(boolean efsEnabled) throws IOException { - if (efsEnabled) { - bootCommand("--set_encrypted_filesystem=on"); - } else { - bootCommand("--set_encrypted_filesystem=off"); - } - } - - /** - * Reboot into the recovery system with the supplied argument. - * @param arg to pass to the recovery utility. - * @throws IOException if something goes wrong. - */ - private static void bootCommand(String arg) throws IOException { - RECOVERY_DIR.mkdirs(); // In case we need it - COMMAND_FILE.delete(); // In case it's not writable - LOG_FILE.delete(); - - FileWriter command = new FileWriter(COMMAND_FILE); - try { - command.write(arg); - command.write("\n"); - } finally { - command.close(); - } - - // Having written the command file, go ahead and reboot - Power.reboot("recovery"); - throw new IOException("Reboot failed (no permissions?)"); - } - - /** - * Called after booting to process and remove recovery-related files. - * @return the log file from recovery, or null if none was found. - */ - public static String handleAftermath() { - // Record the tail of the LOG_FILE - String log = null; - try { - log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n"); - } catch (FileNotFoundException e) { - Log.i(TAG, "No recovery log file"); - } catch (IOException e) { - Log.e(TAG, "Error reading recovery log", e); - } - - // Delete everything in RECOVERY_DIR - String[] names = RECOVERY_DIR.list(); - for (int i = 0; names != null && i < names.length; i++) { - File f = new File(RECOVERY_DIR, names[i]); - if (!f.delete()) { - Log.e(TAG, "Can't delete: " + f); - } else { - Log.i(TAG, "Deleted: " + f); - } - } - - return log; - } -} diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index b7bb72d..57a28e6 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -58,6 +58,10 @@ public class RuntimeInit { /** true if commonInit() has been called */ private static boolean initialized; + private static IBinder mApplicationObject; + + private static volatile boolean mCrashing = false; + /** * Use this to log a message when a thread exits due to an uncaught * exception. The framework catches these for the main threads, so @@ -66,14 +70,30 @@ public class RuntimeInit { private static class UncaughtHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { try { - Log.e(TAG, "Uncaught handler: thread " + t.getName() - + " exiting due to uncaught exception"); - } catch (Throwable error) { - // Ignore the throwable, since we're in the process of crashing anyway. - // If we don't, the crash won't happen properly and the process will - // be left around in a bad state. + // Don't re-enter -- avoid infinite loops if crash-reporting crashes. + if (mCrashing) return; + mCrashing = true; + + if (mApplicationObject == null) { + Log.e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e); + } else { + Log.e(TAG, "FATAL EXCEPTION: " + t.getName(), e); + } + + // Bring up crash dialog, wait for it to be dismissed + ActivityManagerNative.getDefault().handleApplicationCrash( + mApplicationObject, new ApplicationErrorReport.CrashInfo(e)); + } catch (Throwable t2) { + try { + Log.e(TAG, "Error reporting crash", t2); + } catch (Throwable t3) { + // Even Log.e() fails! Oh well. + } + } finally { + // Try everything to make sure this process goes away. + Process.killProcess(Process.myPid()); + System.exit(10); } - crash(TAG, e); } } @@ -300,46 +320,22 @@ public class RuntimeInit { public static native int getQwertyKeyboard(); /** - * Report a fatal error in the current process. If this is a user-process, - * a dialog may be displayed informing the user of the error. This - * function does not return; it forces the current process to exit. + * Report a serious error in the current process. May or may not cause + * the process to terminate (depends on system settings). * - * @param tag to use when logging the error - * @param t exception that was generated by the error + * @param tag to record with the error + * @param t exception describing the error site and conditions */ - public static void crash(String tag, Throwable t) { - if (mApplicationObject != null) { - try { - // Log exception. - Log.e(TAG, Log.getStackTraceString(t)); - - // Show a message to the user. - IActivityManager am = ActivityManagerNative.getDefault(); - am.handleApplicationError(mApplicationObject, tag, - new ApplicationErrorReport.CrashInfo(t)); - } catch (Throwable t2) { - try { - // Log exception as a string so we don't get in an infinite loop. - Log.e(TAG, "Error reporting crash: " + Log.getStackTraceString(t2)); - } catch (Throwable t3) { - // Do nothing, must be OOM so we can't format the message - } - } finally { - // Try everything to make sure this process goes away. - Process.killProcess(Process.myPid()); - System.exit(10); - } - } else { - try { - Log.e(TAG, "*** EXCEPTION IN SYSTEM PROCESS. System will crash."); - Log.e(tag, Log.getStackTraceString(t)); - } catch (Throwable t2) { - // Do nothing, must be OOM so we can't format the message - } finally { - // Try everything to make sure this process goes away. + public static void wtf(String tag, Throwable t) { + try { + if (ActivityManagerNative.getDefault().handleApplicationWtf( + mApplicationObject, tag, new ApplicationErrorReport.CrashInfo(t))) { + // The Activity Manager has already written us off -- now exit. Process.killProcess(Process.myPid()); System.exit(10); } + } catch (Throwable t2) { + Log.e(TAG, "Error reporting WTF", t2); } } @@ -361,6 +357,4 @@ public class RuntimeInit { // Register handlers for DDM messages. android.ddm.DdmRegister.registerHandlers(); } - - private static IBinder mApplicationObject; } diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java deleted file mode 100644 index 592a8fa..0000000 --- a/core/java/com/android/internal/util/FastXmlSerializer.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.util; - -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CoderResult; -import java.nio.charset.IllegalCharsetNameException; -import java.nio.charset.UnsupportedCharsetException; - -/** - * This is a quick and dirty implementation of XmlSerializer that isn't horribly - * painfully slow like the normal one. It only does what is needed for the - * specific XML files being written with it. - */ -public class FastXmlSerializer implements XmlSerializer { - private static final String ESCAPE_TABLE[] = new String[] { - null, null, null, null, null, null, null, null, // 0-7 - null, null, null, null, null, null, null, null, // 8-15 - null, null, null, null, null, null, null, null, // 16-23 - null, null, null, null, null, null, null, null, // 24-31 - null, null, """, null, null, null, "&", null, // 32-39 - null, null, null, null, null, null, null, null, // 40-47 - null, null, null, null, null, null, null, null, // 48-55 - null, null, null, null, "<", null, ">", null, // 56-63 - }; - - private static final int BUFFER_LEN = 8192; - - private final char[] mText = new char[BUFFER_LEN]; - private int mPos; - - private Writer mWriter; - - private OutputStream mOutputStream; - private CharsetEncoder mCharset; - private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN); - - private boolean mInTag; - - private void append(char c) throws IOException { - int pos = mPos; - if (pos >= (BUFFER_LEN-1)) { - flush(); - pos = mPos; - } - mText[pos] = c; - mPos = pos+1; - } - - private void append(String str, int i, final int length) throws IOException { - if (length > BUFFER_LEN) { - final int end = i + length; - while (i < end) { - int next = i + BUFFER_LEN; - append(str, i, next<end ? BUFFER_LEN : (end-i)); - i = next; - } - return; - } - int pos = mPos; - if ((pos+length) > BUFFER_LEN) { - flush(); - pos = mPos; - } - str.getChars(i, i+length, mText, pos); - mPos = pos + length; - } - - private void append(char[] buf, int i, final int length) throws IOException { - if (length > BUFFER_LEN) { - final int end = i + length; - while (i < end) { - int next = i + BUFFER_LEN; - append(buf, i, next<end ? BUFFER_LEN : (end-i)); - i = next; - } - return; - } - int pos = mPos; - if ((pos+length) > BUFFER_LEN) { - flush(); - pos = mPos; - } - System.arraycopy(buf, i, mText, pos, length); - mPos = pos + length; - } - - private void append(String str) throws IOException { - append(str, 0, str.length()); - } - - private void escapeAndAppendString(final String string) throws IOException { - final int N = string.length(); - final char NE = (char)ESCAPE_TABLE.length; - final String[] escapes = ESCAPE_TABLE; - int lastPos = 0; - int pos; - for (pos=0; pos<N; pos++) { - char c = string.charAt(pos); - if (c >= NE) continue; - String escape = escapes[c]; - if (escape == null) continue; - if (lastPos < pos) append(string, lastPos, pos-lastPos); - lastPos = pos + 1; - append(escape); - } - if (lastPos < pos) append(string, lastPos, pos-lastPos); - } - - private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { - final char NE = (char)ESCAPE_TABLE.length; - final String[] escapes = ESCAPE_TABLE; - int end = start+len; - int lastPos = start; - int pos; - for (pos=start; pos<end; pos++) { - char c = buf[pos]; - if (c >= NE) continue; - String escape = escapes[c]; - if (escape == null) continue; - if (lastPos < pos) append(buf, lastPos, pos-lastPos); - lastPos = pos + 1; - append(escape); - } - if (lastPos < pos) append(buf, lastPos, pos-lastPos); - } - - public XmlSerializer attribute(String namespace, String name, String value) throws IOException, - IllegalArgumentException, IllegalStateException { - append(' '); - if (namespace != null) { - append(namespace); - append(':'); - } - append(name); - append("=\""); - - escapeAndAppendString(value); - append('"'); - return this; - } - - public void cdsect(String text) throws IOException, IllegalArgumentException, - IllegalStateException { - throw new UnsupportedOperationException(); - } - - public void comment(String text) throws IOException, IllegalArgumentException, - IllegalStateException { - throw new UnsupportedOperationException(); - } - - public void docdecl(String text) throws IOException, IllegalArgumentException, - IllegalStateException { - throw new UnsupportedOperationException(); - } - - public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { - flush(); - } - - public XmlSerializer endTag(String namespace, String name) throws IOException, - IllegalArgumentException, IllegalStateException { - if (mInTag) { - append(" />\n"); - } else { - append("</"); - if (namespace != null) { - append(namespace); - append(':'); - } - append(name); - append(">\n"); - } - mInTag = false; - return this; - } - - public void entityRef(String text) throws IOException, IllegalArgumentException, - IllegalStateException { - throw new UnsupportedOperationException(); - } - - private void flushBytes() throws IOException { - int position; - if ((position = mBytes.position()) > 0) { - mBytes.flip(); - mOutputStream.write(mBytes.array(), 0, position); - mBytes.clear(); - } - } - - public void flush() throws IOException { - //Log.i("PackageManager", "flush mPos=" + mPos); - if (mPos > 0) { - if (mOutputStream != null) { - CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); - CoderResult result = mCharset.encode(charBuffer, mBytes, true); - while (true) { - if (result.isError()) { - throw new IOException(result.toString()); - } else if (result.isOverflow()) { - flushBytes(); - result = mCharset.encode(charBuffer, mBytes, true); - continue; - } - break; - } - flushBytes(); - mOutputStream.flush(); - } else { - mWriter.write(mText, 0, mPos); - mWriter.flush(); - } - mPos = 0; - } - } - - public int getDepth() { - throw new UnsupportedOperationException(); - } - - public boolean getFeature(String name) { - throw new UnsupportedOperationException(); - } - - public String getName() { - throw new UnsupportedOperationException(); - } - - public String getNamespace() { - throw new UnsupportedOperationException(); - } - - public String getPrefix(String namespace, boolean generatePrefix) - throws IllegalArgumentException { - throw new UnsupportedOperationException(); - } - - public Object getProperty(String name) { - throw new UnsupportedOperationException(); - } - - public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, - IllegalStateException { - throw new UnsupportedOperationException(); - } - - public void processingInstruction(String text) throws IOException, IllegalArgumentException, - IllegalStateException { - throw new UnsupportedOperationException(); - } - - public void setFeature(String name, boolean state) throws IllegalArgumentException, - IllegalStateException { - if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { - return; - } - throw new UnsupportedOperationException(); - } - - public void setOutput(OutputStream os, String encoding) throws IOException, - IllegalArgumentException, IllegalStateException { - if (os == null) - throw new IllegalArgumentException(); - if (true) { - try { - mCharset = Charset.forName(encoding).newEncoder(); - } catch (IllegalCharsetNameException e) { - throw (UnsupportedEncodingException) (new UnsupportedEncodingException( - encoding).initCause(e)); - } catch (UnsupportedCharsetException e) { - throw (UnsupportedEncodingException) (new UnsupportedEncodingException( - encoding).initCause(e)); - } - mOutputStream = os; - } else { - setOutput( - encoding == null - ? new OutputStreamWriter(os) - : new OutputStreamWriter(os, encoding)); - } - } - - public void setOutput(Writer writer) throws IOException, IllegalArgumentException, - IllegalStateException { - mWriter = writer; - } - - public void setPrefix(String prefix, String namespace) throws IOException, - IllegalArgumentException, IllegalStateException { - throw new UnsupportedOperationException(); - } - - public void setProperty(String name, Object value) throws IllegalArgumentException, - IllegalStateException { - throw new UnsupportedOperationException(); - } - - public void startDocument(String encoding, Boolean standalone) throws IOException, - IllegalArgumentException, IllegalStateException { - append("<?xml version='1.0' encoding='utf-8' standalone='" - + (standalone ? "yes" : "no") + "' ?>\n"); - } - - public XmlSerializer startTag(String namespace, String name) throws IOException, - IllegalArgumentException, IllegalStateException { - if (mInTag) { - append(">\n"); - } - append('<'); - if (namespace != null) { - append(namespace); - append(':'); - } - append(name); - mInTag = true; - return this; - } - - public XmlSerializer text(char[] buf, int start, int len) throws IOException, - IllegalArgumentException, IllegalStateException { - if (mInTag) { - append(">"); - mInTag = false; - } - escapeAndAppendString(buf, start, len); - return this; - } - - public XmlSerializer text(String text) throws IOException, IllegalArgumentException, - IllegalStateException { - if (mInTag) { - append(">"); - mInTag = false; - } - escapeAndAppendString(text); - return this; - } - -} diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java deleted file mode 100644 index 948e313..0000000 --- a/core/java/com/android/internal/util/XmlUtils.java +++ /dev/null @@ -1,796 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.util; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import android.util.Xml; - -/** {@hide} */ -public class XmlUtils -{ - - public static void skipCurrentTag(XmlPullParser parser) - throws XmlPullParserException, IOException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - } - } - - public static final int - convertValueToList(CharSequence value, String[] options, int defaultValue) - { - if (null != value) { - for (int i = 0; i < options.length; i++) { - if (value.equals(options[i])) - return i; - } - } - - return defaultValue; - } - - public static final boolean - convertValueToBoolean(CharSequence value, boolean defaultValue) - { - boolean result = false; - - if (null == value) - return defaultValue; - - if (value.equals("1") - || value.equals("true") - || value.equals("TRUE")) - result = true; - - return result; - } - - public static final int - convertValueToInt(CharSequence charSeq, int defaultValue) - { - if (null == charSeq) - return defaultValue; - - String nm = charSeq.toString(); - - // XXX This code is copied from Integer.decode() so we don't - // have to instantiate an Integer! - - int value; - int sign = 1; - int index = 0; - int len = nm.length(); - int base = 10; - - if ('-' == nm.charAt(0)) { - sign = -1; - index++; - } - - if ('0' == nm.charAt(index)) { - // Quick check for a zero by itself - if (index == (len - 1)) - return 0; - - char c = nm.charAt(index + 1); - - if ('x' == c || 'X' == c) { - index += 2; - base = 16; - } else { - index++; - base = 8; - } - } - else if ('#' == nm.charAt(index)) - { - index++; - base = 16; - } - - return Integer.parseInt(nm.substring(index), base) * sign; - } - - public static final int - convertValueToUnsignedInt(String value, int defaultValue) - { - if (null == value) - return defaultValue; - - return parseUnsignedIntAttribute(value); - } - - public static final int - parseUnsignedIntAttribute(CharSequence charSeq) - { - String value = charSeq.toString(); - - long bits; - int index = 0; - int len = value.length(); - int base = 10; - - if ('0' == value.charAt(index)) { - // Quick check for zero by itself - if (index == (len - 1)) - return 0; - - char c = value.charAt(index + 1); - - if ('x' == c || 'X' == c) { // check for hex - index += 2; - base = 16; - } else { // check for octal - index++; - base = 8; - } - } else if ('#' == value.charAt(index)) { - index++; - base = 16; - } - - return (int) Long.parseLong(value.substring(index), base); - } - - /** - * Flatten a Map into an output stream as XML. The map can later be - * read back with readMapXml(). - * - * @param val The map to be flattened. - * @param out Where to write the XML data. - * - * @see #writeMapXml(Map, String, XmlSerializer) - * @see #writeListXml - * @see #writeValueXml - * @see #readMapXml - */ - public static final void writeMapXml(Map val, OutputStream out) - throws XmlPullParserException, java.io.IOException { - XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(out, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - writeMapXml(val, null, serializer); - serializer.endDocument(); - } - - /** - * Flatten a List into an output stream as XML. The list can later be - * read back with readListXml(). - * - * @param val The list to be flattened. - * @param out Where to write the XML data. - * - * @see #writeListXml(List, String, XmlSerializer) - * @see #writeMapXml - * @see #writeValueXml - * @see #readListXml - */ - public static final void writeListXml(List val, OutputStream out) - throws XmlPullParserException, java.io.IOException - { - XmlSerializer serializer = Xml.newSerializer(); - serializer.setOutput(out, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - writeListXml(val, null, serializer); - serializer.endDocument(); - } - - /** - * Flatten a Map into an XmlSerializer. The map can later be read back - * with readThisMapXml(). - * - * @param val The map to be flattened. - * @param name Name attribute to include with this list's tag, or null for - * none. - * @param out XmlSerializer to write the map into. - * - * @see #writeMapXml(Map, OutputStream) - * @see #writeListXml - * @see #writeValueXml - * @see #readMapXml - */ - public static final void writeMapXml(Map val, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { - if (val == null) { - out.startTag(null, "null"); - out.endTag(null, "null"); - return; - } - - Set s = val.entrySet(); - Iterator i = s.iterator(); - - out.startTag(null, "map"); - if (name != null) { - out.attribute(null, "name", name); - } - - while (i.hasNext()) { - Map.Entry e = (Map.Entry)i.next(); - writeValueXml(e.getValue(), (String)e.getKey(), out); - } - - out.endTag(null, "map"); - } - - /** - * Flatten a List into an XmlSerializer. The list can later be read back - * with readThisListXml(). - * - * @param val The list to be flattened. - * @param name Name attribute to include with this list's tag, or null for - * none. - * @param out XmlSerializer to write the list into. - * - * @see #writeListXml(List, OutputStream) - * @see #writeMapXml - * @see #writeValueXml - * @see #readListXml - */ - public static final void writeListXml(List val, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { - if (val == null) { - out.startTag(null, "null"); - out.endTag(null, "null"); - return; - } - - out.startTag(null, "list"); - if (name != null) { - out.attribute(null, "name", name); - } - - int N = val.size(); - int i=0; - while (i < N) { - writeValueXml(val.get(i), null, out); - i++; - } - - out.endTag(null, "list"); - } - - /** - * Flatten a byte[] into an XmlSerializer. The list can later be read back - * with readThisByteArrayXml(). - * - * @param val The byte array to be flattened. - * @param name Name attribute to include with this array's tag, or null for - * none. - * @param out XmlSerializer to write the array into. - * - * @see #writeMapXml - * @see #writeValueXml - */ - public static final void writeByteArrayXml(byte[] val, String name, - XmlSerializer out) - throws XmlPullParserException, java.io.IOException { - - if (val == null) { - out.startTag(null, "null"); - out.endTag(null, "null"); - return; - } - - out.startTag(null, "byte-array"); - if (name != null) { - out.attribute(null, "name", name); - } - - final int N = val.length; - out.attribute(null, "num", Integer.toString(N)); - - StringBuilder sb = new StringBuilder(val.length*2); - for (int i=0; i<N; i++) { - int b = val[i]; - int h = b>>4; - sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); - h = b&0xff; - sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); - } - - out.text(sb.toString()); - - out.endTag(null, "byte-array"); - } - - /** - * Flatten an int[] into an XmlSerializer. The list can later be read back - * with readThisIntArrayXml(). - * - * @param val The int array to be flattened. - * @param name Name attribute to include with this array's tag, or null for - * none. - * @param out XmlSerializer to write the array into. - * - * @see #writeMapXml - * @see #writeValueXml - * @see #readThisIntArrayXml - */ - public static final void writeIntArrayXml(int[] val, String name, - XmlSerializer out) - throws XmlPullParserException, java.io.IOException { - - if (val == null) { - out.startTag(null, "null"); - out.endTag(null, "null"); - return; - } - - out.startTag(null, "int-array"); - if (name != null) { - out.attribute(null, "name", name); - } - - final int N = val.length; - out.attribute(null, "num", Integer.toString(N)); - - for (int i=0; i<N; i++) { - out.startTag(null, "item"); - out.attribute(null, "value", Integer.toString(val[i])); - out.endTag(null, "item"); - } - - out.endTag(null, "int-array"); - } - - /** - * Flatten an object's value into an XmlSerializer. The value can later - * be read back with readThisValueXml(). - * - * Currently supported value types are: null, String, Integer, Long, - * Float, Double Boolean, Map, List. - * - * @param v The object to be flattened. - * @param name Name attribute to include with this value's tag, or null - * for none. - * @param out XmlSerializer to write the object into. - * - * @see #writeMapXml - * @see #writeListXml - * @see #readValueXml - */ - public static final void writeValueXml(Object v, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { - String typeStr; - if (v == null) { - out.startTag(null, "null"); - if (name != null) { - out.attribute(null, "name", name); - } - out.endTag(null, "null"); - return; - } else if (v instanceof String) { - out.startTag(null, "string"); - if (name != null) { - out.attribute(null, "name", name); - } - out.text(v.toString()); - out.endTag(null, "string"); - return; - } else if (v instanceof Integer) { - typeStr = "int"; - } else if (v instanceof Long) { - typeStr = "long"; - } else if (v instanceof Float) { - typeStr = "float"; - } else if (v instanceof Double) { - typeStr = "double"; - } else if (v instanceof Boolean) { - typeStr = "boolean"; - } else if (v instanceof byte[]) { - writeByteArrayXml((byte[])v, name, out); - return; - } else if (v instanceof int[]) { - writeIntArrayXml((int[])v, name, out); - return; - } else if (v instanceof Map) { - writeMapXml((Map)v, name, out); - return; - } else if (v instanceof List) { - writeListXml((List)v, name, out); - return; - } else if (v instanceof CharSequence) { - // XXX This is to allow us to at least write something if - // we encounter styled text... but it means we will drop all - // of the styling information. :( - out.startTag(null, "string"); - if (name != null) { - out.attribute(null, "name", name); - } - out.text(v.toString()); - out.endTag(null, "string"); - return; - } else { - throw new RuntimeException("writeValueXml: unable to write value " + v); - } - - out.startTag(null, typeStr); - if (name != null) { - out.attribute(null, "name", name); - } - out.attribute(null, "value", v.toString()); - out.endTag(null, typeStr); - } - - /** - * Read a HashMap from an InputStream containing XML. The stream can - * previously have been written by writeMapXml(). - * - * @param in The InputStream from which to read. - * - * @return HashMap The resulting map. - * - * @see #readListXml - * @see #readValueXml - * @see #readThisMapXml - * #see #writeMapXml - */ - public static final HashMap readMapXml(InputStream in) - throws XmlPullParserException, java.io.IOException - { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, null); - return (HashMap)readValueXml(parser, new String[1]); - } - - /** - * Read an ArrayList from an InputStream containing XML. The stream can - * previously have been written by writeListXml(). - * - * @param in The InputStream from which to read. - * - * @return HashMap The resulting list. - * - * @see #readMapXml - * @see #readValueXml - * @see #readThisListXml - * @see #writeListXml - */ - public static final ArrayList readListXml(InputStream in) - throws XmlPullParserException, java.io.IOException - { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, null); - return (ArrayList)readValueXml(parser, new String[1]); - } - - /** - * Read a HashMap object from an XmlPullParser. The XML data could - * previously have been generated by writeMapXml(). The XmlPullParser - * must be positioned <em>after</em> the tag that begins the map. - * - * @param parser The XmlPullParser from which to read the map data. - * @param endTag Name of the tag that will end the map, usually "map". - * @param name An array of one string, used to return the name attribute - * of the map's tag. - * - * @return HashMap The newly generated map. - * - * @see #readMapXml - */ - public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException - { - HashMap map = new HashMap(); - - int eventType = parser.getEventType(); - do { - if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); - if (name[0] != null) { - //System.out.println("Adding to map: " + name + " -> " + val); - map.put(name[0], val); - } else { - throw new XmlPullParserException( - "Map value without name attribute: " + parser.getName()); - } - } else if (eventType == parser.END_TAG) { - if (parser.getName().equals(endTag)) { - return map; - } - throw new XmlPullParserException( - "Expected " + endTag + " end tag at: " + parser.getName()); - } - eventType = parser.next(); - } while (eventType != parser.END_DOCUMENT); - - throw new XmlPullParserException( - "Document ended before " + endTag + " end tag"); - } - - /** - * Read an ArrayList object from an XmlPullParser. The XML data could - * previously have been generated by writeListXml(). The XmlPullParser - * must be positioned <em>after</em> the tag that begins the list. - * - * @param parser The XmlPullParser from which to read the list data. - * @param endTag Name of the tag that will end the list, usually "list". - * @param name An array of one string, used to return the name attribute - * of the list's tag. - * - * @return HashMap The newly generated list. - * - * @see #readListXml - */ - public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException - { - ArrayList list = new ArrayList(); - - int eventType = parser.getEventType(); - do { - if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); - list.add(val); - //System.out.println("Adding to list: " + val); - } else if (eventType == parser.END_TAG) { - if (parser.getName().equals(endTag)) { - return list; - } - throw new XmlPullParserException( - "Expected " + endTag + " end tag at: " + parser.getName()); - } - eventType = parser.next(); - } while (eventType != parser.END_DOCUMENT); - - throw new XmlPullParserException( - "Document ended before " + endTag + " end tag"); - } - - /** - * Read an int[] object from an XmlPullParser. The XML data could - * previously have been generated by writeIntArrayXml(). The XmlPullParser - * must be positioned <em>after</em> the tag that begins the list. - * - * @param parser The XmlPullParser from which to read the list data. - * @param endTag Name of the tag that will end the list, usually "list". - * @param name An array of one string, used to return the name attribute - * of the list's tag. - * - * @return Returns a newly generated int[]. - * - * @see #readListXml - */ - public static final int[] readThisIntArrayXml(XmlPullParser parser, - String endTag, String[] name) - throws XmlPullParserException, java.io.IOException { - - int num; - try { - num = Integer.parseInt(parser.getAttributeValue(null, "num")); - } catch (NullPointerException e) { - throw new XmlPullParserException( - "Need num attribute in byte-array"); - } catch (NumberFormatException e) { - throw new XmlPullParserException( - "Not a number in num attribute in byte-array"); - } - - int[] array = new int[num]; - int i = 0; - - int eventType = parser.getEventType(); - do { - if (eventType == parser.START_TAG) { - if (parser.getName().equals("item")) { - try { - array[i] = Integer.parseInt( - parser.getAttributeValue(null, "value")); - } catch (NullPointerException e) { - throw new XmlPullParserException( - "Need value attribute in item"); - } catch (NumberFormatException e) { - throw new XmlPullParserException( - "Not a number in value attribute in item"); - } - } else { - throw new XmlPullParserException( - "Expected item tag at: " + parser.getName()); - } - } else if (eventType == parser.END_TAG) { - if (parser.getName().equals(endTag)) { - return array; - } else if (parser.getName().equals("item")) { - i++; - } else { - throw new XmlPullParserException( - "Expected " + endTag + " end tag at: " - + parser.getName()); - } - } - eventType = parser.next(); - } while (eventType != parser.END_DOCUMENT); - - throw new XmlPullParserException( - "Document ended before " + endTag + " end tag"); - } - - /** - * Read a flattened object from an XmlPullParser. The XML data could - * previously have been written with writeMapXml(), writeListXml(), or - * writeValueXml(). The XmlPullParser must be positioned <em>at</em> the - * tag that defines the value. - * - * @param parser The XmlPullParser from which to read the object. - * @param name An array of one string, used to return the name attribute - * of the value's tag. - * - * @return Object The newly generated value object. - * - * @see #readMapXml - * @see #readListXml - * @see #writeValueXml - */ - public static final Object readValueXml(XmlPullParser parser, String[] name) - throws XmlPullParserException, java.io.IOException - { - int eventType = parser.getEventType(); - do { - if (eventType == parser.START_TAG) { - return readThisValueXml(parser, name); - } else if (eventType == parser.END_TAG) { - throw new XmlPullParserException( - "Unexpected end tag at: " + parser.getName()); - } else if (eventType == parser.TEXT) { - throw new XmlPullParserException( - "Unexpected text: " + parser.getText()); - } - eventType = parser.next(); - } while (eventType != parser.END_DOCUMENT); - - throw new XmlPullParserException( - "Unexpected end of document"); - } - - private static final Object readThisValueXml(XmlPullParser parser, String[] name) - throws XmlPullParserException, java.io.IOException - { - final String valueName = parser.getAttributeValue(null, "name"); - final String tagName = parser.getName(); - - //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName); - - Object res; - - if (tagName.equals("null")) { - res = null; - } else if (tagName.equals("string")) { - String value = ""; - int eventType; - while ((eventType = parser.next()) != parser.END_DOCUMENT) { - if (eventType == parser.END_TAG) { - if (parser.getName().equals("string")) { - name[0] = valueName; - //System.out.println("Returning value for " + valueName + ": " + value); - return value; - } - throw new XmlPullParserException( - "Unexpected end tag in <string>: " + parser.getName()); - } else if (eventType == parser.TEXT) { - value += parser.getText(); - } else if (eventType == parser.START_TAG) { - throw new XmlPullParserException( - "Unexpected start tag in <string>: " + parser.getName()); - } - } - throw new XmlPullParserException( - "Unexpected end of document in <string>"); - } else if (tagName.equals("int")) { - res = Integer.parseInt(parser.getAttributeValue(null, "value")); - } else if (tagName.equals("long")) { - res = Long.valueOf(parser.getAttributeValue(null, "value")); - } else if (tagName.equals("float")) { - res = new Float(parser.getAttributeValue(null, "value")); - } else if (tagName.equals("double")) { - res = new Double(parser.getAttributeValue(null, "value")); - } else if (tagName.equals("boolean")) { - res = Boolean.valueOf(parser.getAttributeValue(null, "value")); - } else if (tagName.equals("int-array")) { - parser.next(); - res = readThisIntArrayXml(parser, "int-array", name); - name[0] = valueName; - //System.out.println("Returning value for " + valueName + ": " + res); - return res; - } else if (tagName.equals("map")) { - parser.next(); - res = readThisMapXml(parser, "map", name); - name[0] = valueName; - //System.out.println("Returning value for " + valueName + ": " + res); - return res; - } else if (tagName.equals("list")) { - parser.next(); - res = readThisListXml(parser, "list", name); - name[0] = valueName; - //System.out.println("Returning value for " + valueName + ": " + res); - return res; - } else { - throw new XmlPullParserException( - "Unknown tag: " + tagName); - } - - // Skip through to end tag. - int eventType; - while ((eventType = parser.next()) != parser.END_DOCUMENT) { - if (eventType == parser.END_TAG) { - if (parser.getName().equals(tagName)) { - name[0] = valueName; - //System.out.println("Returning value for " + valueName + ": " + res); - return res; - } - throw new XmlPullParserException( - "Unexpected end tag in <" + tagName + ">: " + parser.getName()); - } else if (eventType == parser.TEXT) { - throw new XmlPullParserException( - "Unexpected text in <" + tagName + ">: " + parser.getName()); - } else if (eventType == parser.START_TAG) { - throw new XmlPullParserException( - "Unexpected start tag in <" + tagName + ">: " + parser.getName()); - } - } - throw new XmlPullParserException( - "Unexpected end of document in <" + tagName + ">"); - } - - public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException - { - int type; - while ((type=parser.next()) != parser.START_TAG - && type != parser.END_DOCUMENT) { - ; - } - - if (type != parser.START_TAG) { - throw new XmlPullParserException("No start tag found"); - } - - if (!parser.getName().equals(firstElementName)) { - throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + - ", expected " + firstElementName); - } - } - - public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException - { - int type; - while ((type=parser.next()) != parser.START_TAG - && type != parser.END_DOCUMENT) { - ; - } - } -} diff --git a/core/java/com/android/internal/widget/VerticalTextSpinner.java b/core/java/com/android/internal/widget/VerticalTextSpinner.java deleted file mode 100644 index 50c528c..0000000 --- a/core/java/com/android/internal/widget/VerticalTextSpinner.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.text.TextPaint; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; - - -public class VerticalTextSpinner extends View { - - private static final int SELECTOR_ARROW_HEIGHT = 15; - - private static final int TEXT_SPACING = 18; - private static final int TEXT_MARGIN_RIGHT = 25; - private static final int TEXT_SIZE = 22; - - /* Keep the calculations as this is really a for loop from - * -2 to 2 but precalculated so we don't have to do in the onDraw. - */ - private static final int TEXT1_Y = (TEXT_SIZE * (-2 + 2)) + (TEXT_SPACING * (-2 + 1)); - private static final int TEXT2_Y = (TEXT_SIZE * (-1 + 2)) + (TEXT_SPACING * (-1 + 1)); - private static final int TEXT3_Y = (TEXT_SIZE * (0 + 2)) + (TEXT_SPACING * (0 + 1)); - private static final int TEXT4_Y = (TEXT_SIZE * (1 + 2)) + (TEXT_SPACING * (1 + 1)); - private static final int TEXT5_Y = (TEXT_SIZE * (2 + 2)) + (TEXT_SPACING * (2 + 1)); - - private static final int SCROLL_MODE_NONE = 0; - private static final int SCROLL_MODE_UP = 1; - private static final int SCROLL_MODE_DOWN = 2; - - private static final long DEFAULT_SCROLL_INTERVAL_MS = 400; - private static final int SCROLL_DISTANCE = TEXT_SIZE + TEXT_SPACING; - private static final int MIN_ANIMATIONS = 4; - - private final Drawable mBackgroundFocused; - private final Drawable mSelectorFocused; - private final Drawable mSelectorNormal; - private final int mSelectorDefaultY; - private final int mSelectorMinY; - private final int mSelectorMaxY; - private final int mSelectorHeight; - private final TextPaint mTextPaintDark; - private final TextPaint mTextPaintLight; - - private int mSelectorY; - private Drawable mSelector; - private int mDownY; - private boolean isDraggingSelector; - private int mScrollMode; - private long mScrollInterval; - private boolean mIsAnimationRunning; - private boolean mStopAnimation; - private boolean mWrapAround = true; - - private int mTotalAnimatedDistance; - private int mNumberOfAnimations; - private long mDelayBetweenAnimations; - private int mDistanceOfEachAnimation; - - private String[] mTextList; - private int mCurrentSelectedPos; - private OnChangedListener mListener; - - private String mText1; - private String mText2; - private String mText3; - private String mText4; - private String mText5; - - public interface OnChangedListener { - void onChanged( - VerticalTextSpinner spinner, int oldPos, int newPos, String[] items); - } - - public VerticalTextSpinner(Context context) { - this(context, null); - } - - public VerticalTextSpinner(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public VerticalTextSpinner(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - - mBackgroundFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_background); - mSelectorFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_selected); - mSelectorNormal = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_unselected); - - mSelectorHeight = mSelectorFocused.getIntrinsicHeight(); - mSelectorDefaultY = (mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight) / 2; - mSelectorMinY = 0; - mSelectorMaxY = mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight; - - mSelector = mSelectorNormal; - mSelectorY = mSelectorDefaultY; - - mTextPaintDark = new TextPaint(Paint.ANTI_ALIAS_FLAG); - mTextPaintDark.setTextSize(TEXT_SIZE); - mTextPaintDark.setColor(context.getResources().getColor(com.android.internal.R.color.primary_text_light)); - - mTextPaintLight = new TextPaint(Paint.ANTI_ALIAS_FLAG); - mTextPaintLight.setTextSize(TEXT_SIZE); - mTextPaintLight.setColor(context.getResources().getColor(com.android.internal.R.color.secondary_text_dark)); - - mScrollMode = SCROLL_MODE_NONE; - mScrollInterval = DEFAULT_SCROLL_INTERVAL_MS; - calculateAnimationValues(); - } - - public void setOnChangeListener(OnChangedListener listener) { - mListener = listener; - } - - public void setItems(String[] textList) { - mTextList = textList; - calculateTextPositions(); - } - - public void setSelectedPos(int selectedPos) { - mCurrentSelectedPos = selectedPos; - calculateTextPositions(); - postInvalidate(); - } - - public void setScrollInterval(long interval) { - mScrollInterval = interval; - calculateAnimationValues(); - } - - public void setWrapAround(boolean wrap) { - mWrapAround = wrap; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - - /* This is a bit confusing, when we get the key event - * DPAD_DOWN we actually roll the spinner up. When the - * key event is DPAD_UP we roll the spinner down. - */ - if ((keyCode == KeyEvent.KEYCODE_DPAD_UP) && canScrollDown()) { - mScrollMode = SCROLL_MODE_DOWN; - scroll(); - mStopAnimation = true; - return true; - } else if ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && canScrollUp()) { - mScrollMode = SCROLL_MODE_UP; - scroll(); - mStopAnimation = true; - return true; - } - return super.onKeyDown(keyCode, event); - } - - private boolean canScrollDown() { - return (mCurrentSelectedPos > 0) || mWrapAround; - } - - private boolean canScrollUp() { - return ((mCurrentSelectedPos < (mTextList.length - 1)) || mWrapAround); - } - - @Override - protected void onFocusChanged(boolean gainFocus, int direction, - Rect previouslyFocusedRect) { - if (gainFocus) { - setBackgroundDrawable(mBackgroundFocused); - mSelector = mSelectorFocused; - } else { - setBackgroundDrawable(null); - mSelector = mSelectorNormal; - mSelectorY = mSelectorDefaultY; - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - final int action = event.getAction(); - final int y = (int) event.getY(); - - switch (action) { - case MotionEvent.ACTION_DOWN: - requestFocus(); - mDownY = y; - isDraggingSelector = (y >= mSelectorY) && (y <= (mSelectorY + mSelector.getIntrinsicHeight())); - break; - - case MotionEvent.ACTION_MOVE: - if (isDraggingSelector) { - int top = mSelectorDefaultY + (y - mDownY); - if (top <= mSelectorMinY && canScrollDown()) { - mSelectorY = mSelectorMinY; - mStopAnimation = false; - if (mScrollMode != SCROLL_MODE_DOWN) { - mScrollMode = SCROLL_MODE_DOWN; - scroll(); - } - } else if (top >= mSelectorMaxY && canScrollUp()) { - mSelectorY = mSelectorMaxY; - mStopAnimation = false; - if (mScrollMode != SCROLL_MODE_UP) { - mScrollMode = SCROLL_MODE_UP; - scroll(); - } - } else { - mSelectorY = top; - mStopAnimation = true; - } - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - default: - mSelectorY = mSelectorDefaultY; - mStopAnimation = true; - invalidate(); - break; - } - return true; - } - - @Override - protected void onDraw(Canvas canvas) { - - /* The bounds of the selector */ - final int selectorLeft = 0; - final int selectorTop = mSelectorY; - final int selectorRight = mMeasuredWidth; - final int selectorBottom = mSelectorY + mSelectorHeight; - - /* Draw the selector */ - mSelector.setBounds(selectorLeft, selectorTop, selectorRight, selectorBottom); - mSelector.draw(canvas); - - if (mTextList == null) { - - /* We're not setup with values so don't draw anything else */ - return; - } - - final TextPaint textPaintDark = mTextPaintDark; - if (hasFocus()) { - - /* The bounds of the top area where the text should be light */ - final int topLeft = 0; - final int topTop = 0; - final int topRight = selectorRight; - final int topBottom = selectorTop + SELECTOR_ARROW_HEIGHT; - - /* Assign a bunch of local finals for performance */ - final String text1 = mText1; - final String text2 = mText2; - final String text3 = mText3; - final String text4 = mText4; - final String text5 = mText5; - final TextPaint textPaintLight = mTextPaintLight; - - /* - * Draw the 1st, 2nd and 3rd item in light only, clip it so it only - * draws in the area above the selector - */ - canvas.save(); - canvas.clipRect(topLeft, topTop, topRight, topBottom); - drawText(canvas, text1, TEXT1_Y - + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text2, TEXT2_Y - + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text3, - TEXT3_Y + mTotalAnimatedDistance, textPaintLight); - canvas.restore(); - - /* - * Draw the 2nd, 3rd and 4th clipped to the selector bounds in dark - * paint - */ - canvas.save(); - canvas.clipRect(selectorLeft, selectorTop + SELECTOR_ARROW_HEIGHT, - selectorRight, selectorBottom - SELECTOR_ARROW_HEIGHT); - drawText(canvas, text2, TEXT2_Y - + mTotalAnimatedDistance, textPaintDark); - drawText(canvas, text3, - TEXT3_Y + mTotalAnimatedDistance, textPaintDark); - drawText(canvas, text4, - TEXT4_Y + mTotalAnimatedDistance, textPaintDark); - canvas.restore(); - - /* The bounds of the bottom area where the text should be light */ - final int bottomLeft = 0; - final int bottomTop = selectorBottom - SELECTOR_ARROW_HEIGHT; - final int bottomRight = selectorRight; - final int bottomBottom = mMeasuredHeight; - - /* - * Draw the 3rd, 4th and 5th in white text, clip it so it only draws - * in the area below the selector. - */ - canvas.save(); - canvas.clipRect(bottomLeft, bottomTop, bottomRight, bottomBottom); - drawText(canvas, text3, - TEXT3_Y + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text4, - TEXT4_Y + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text5, - TEXT5_Y + mTotalAnimatedDistance, textPaintLight); - canvas.restore(); - - } else { - drawText(canvas, mText3, TEXT3_Y, textPaintDark); - } - if (mIsAnimationRunning) { - if ((Math.abs(mTotalAnimatedDistance) + mDistanceOfEachAnimation) > SCROLL_DISTANCE) { - mTotalAnimatedDistance = 0; - if (mScrollMode == SCROLL_MODE_UP) { - int oldPos = mCurrentSelectedPos; - int newPos = getNewIndex(1); - if (newPos >= 0) { - mCurrentSelectedPos = newPos; - if (mListener != null) { - mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList); - } - } - if (newPos < 0 || ((newPos >= mTextList.length - 1) && !mWrapAround)) { - mStopAnimation = true; - } - calculateTextPositions(); - } else if (mScrollMode == SCROLL_MODE_DOWN) { - int oldPos = mCurrentSelectedPos; - int newPos = getNewIndex(-1); - if (newPos >= 0) { - mCurrentSelectedPos = newPos; - if (mListener != null) { - mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList); - } - } - if (newPos < 0 || (newPos == 0 && !mWrapAround)) { - mStopAnimation = true; - } - calculateTextPositions(); - } - if (mStopAnimation) { - final int previousScrollMode = mScrollMode; - - /* No longer scrolling, we wait till the current animation - * completes then we stop. - */ - mIsAnimationRunning = false; - mStopAnimation = false; - mScrollMode = SCROLL_MODE_NONE; - - /* If the current selected item is an empty string - * scroll past it. - */ - if ("".equals(mTextList[mCurrentSelectedPos])) { - mScrollMode = previousScrollMode; - scroll(); - mStopAnimation = true; - } - } - } else { - if (mScrollMode == SCROLL_MODE_UP) { - mTotalAnimatedDistance -= mDistanceOfEachAnimation; - } else if (mScrollMode == SCROLL_MODE_DOWN) { - mTotalAnimatedDistance += mDistanceOfEachAnimation; - } - } - if (mDelayBetweenAnimations > 0) { - postInvalidateDelayed(mDelayBetweenAnimations); - } else { - invalidate(); - } - } - } - - /** - * Called every time the text items or current position - * changes. We calculate store we don't have to calculate - * onDraw. - */ - private void calculateTextPositions() { - mText1 = getTextToDraw(-2); - mText2 = getTextToDraw(-1); - mText3 = getTextToDraw(0); - mText4 = getTextToDraw(1); - mText5 = getTextToDraw(2); - } - - private String getTextToDraw(int offset) { - int index = getNewIndex(offset); - if (index < 0) { - return ""; - } - return mTextList[index]; - } - - private int getNewIndex(int offset) { - int index = mCurrentSelectedPos + offset; - if (index < 0) { - if (mWrapAround) { - index += mTextList.length; - } else { - return -1; - } - } else if (index >= mTextList.length) { - if (mWrapAround) { - index -= mTextList.length; - } else { - return -1; - } - } - return index; - } - - private void scroll() { - if (mIsAnimationRunning) { - return; - } - mTotalAnimatedDistance = 0; - mIsAnimationRunning = true; - invalidate(); - } - - private void calculateAnimationValues() { - mNumberOfAnimations = (int) mScrollInterval / SCROLL_DISTANCE; - if (mNumberOfAnimations < MIN_ANIMATIONS) { - mNumberOfAnimations = MIN_ANIMATIONS; - mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations; - mDelayBetweenAnimations = 0; - } else { - mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations; - mDelayBetweenAnimations = mScrollInterval / mNumberOfAnimations; - } - } - - private void drawText(Canvas canvas, String text, int y, TextPaint paint) { - int width = (int) paint.measureText(text); - int x = getMeasuredWidth() - width - TEXT_MARGIN_RIGHT; - canvas.drawText(text, x, y, paint); - } - - public int getCurrentSelectedPos() { - return mCurrentSelectedPos; - } -} diff --git a/core/java/com/google/android/net/NetworkStatsEntity.java b/core/java/com/google/android/net/NetworkStatsEntity.java deleted file mode 100644 index a22fa1e..0000000 --- a/core/java/com/google/android/net/NetworkStatsEntity.java +++ /dev/null @@ -1,84 +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 com.google.android.net; - -import android.net.TrafficStats; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.util.EventLog; -import org.apache.http.HttpEntity; -import org.apache.http.entity.HttpEntityWrapper; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - - -public class NetworkStatsEntity extends HttpEntityWrapper { - - private static final int HTTP_STATS_EVENT = 52001; - - private class NetworkStatsInputStream extends FilterInputStream { - - public NetworkStatsInputStream(InputStream wrapped) { - super(wrapped); - } - - @Override - public void close() throws IOException { - try { - super.close(); - } finally { - long processingTime = SystemClock.elapsedRealtime() - mProcessingStartTime; - long tx = TrafficStats.getUidTxBytes(mUid); - long rx = TrafficStats.getUidRxBytes(mUid); - - EventLog.writeEvent(HTTP_STATS_EVENT, mUa, mResponseLatency, processingTime, - tx - mStartTx, rx - mStartRx); - } - } - } - - private final String mUa; - private final int mUid; - private final long mStartTx; - private final long mStartRx; - private final long mResponseLatency; - private final long mProcessingStartTime; - - public NetworkStatsEntity(HttpEntity orig, String ua, - int uid, long startTx, long startRx, long responseLatency, - long processingStartTime) { - super(orig); - this.mUa = ua; - this.mUid = uid; - this.mStartTx = startTx; - this.mStartRx = startRx; - this.mResponseLatency = responseLatency; - this.mProcessingStartTime = processingStartTime; - } - - public static boolean shouldLogNetworkStats() { - return "1".equals(SystemProperties.get("googlehttpclient.logstats")); - } - - @Override - public InputStream getContent() throws IOException { - InputStream orig = super.getContent(); - return new NetworkStatsInputStream(orig); - } -} diff --git a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java b/core/java/com/google/android/net/SSLClientSessionCacheFactory.java deleted file mode 100644 index 6570a9bd..0000000 --- a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.google.android.net; - -import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; -import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache; -import android.content.Context; -import android.provider.Settings; -import android.util.Log; - -import java.io.File; -import java.io.IOException; - -import com.android.internal.net.DbSSLSessionCache; - -/** - * Factory that returns the appropriate implementation of a {@link SSLClientSessionCache} based - * on gservices. - * - * @hide - */ -// TODO: return a proxied implementation that is updated as the gservices value changes. -public final class SSLClientSessionCacheFactory { - - private static final String TAG = "SSLClientSessionCacheFactory"; - - public static final String DB = "db"; - public static final String FILE = "file"; - - // utility class - private SSLClientSessionCacheFactory() {} - - /** - * Returns a new {@link SSLClientSessionCache} based on the persistent cache that's specified, - * if any, in gservices. If no cache is specified, returns null. - * @param context The application context used for the per-process persistent cache. - * @return A new {@link SSLClientSessionCache}, or null if no persistent cache is configured. - */ - public static SSLClientSessionCache getCache(Context context) { - String type = Settings.Gservices.getString(context.getContentResolver(), - Settings.Gservices.SSL_SESSION_CACHE); - - if (type != null) { - if (DB.equals(type)) { - return DbSSLSessionCache.getInstanceForPackage(context); - } else if (FILE.equals(type)) { - File dir = context.getFilesDir(); - File cacheDir = new File(dir, "sslcache"); - if (!cacheDir.exists()) { - cacheDir.mkdir(); - } - try { - return FileClientSessionCache.usingDirectory(cacheDir); - } catch (IOException ioe) { - Log.w(TAG, "Unable to create FileClientSessionCache in " + cacheDir.getName(), ioe); - return null; - } - } else { - Log.w(TAG, "Ignoring unrecognized type: '" + type + "'"); - } - } - return null; - } -} diff --git a/core/java/com/google/android/net/UrlRules.java b/core/java/com/google/android/net/UrlRules.java deleted file mode 100644 index 54d139d..0000000 --- a/core/java/com/google/android/net/UrlRules.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.net; - -import android.content.ContentResolver; -import android.database.Cursor; -import android.provider.Checkin; -import android.provider.Settings; -import android.util.Config; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A set of rules rewriting and blocking URLs. Used to offer a point of - * control for redirecting HTTP requests, often to the Android proxy server. - * - * <p>Each rule has the following format: - * - * <pre><em>url-prefix</em> [REWRITE <em>new-prefix</em>] [BLOCK]</pre> - * - * <p>Any URL which starts with <em>url-prefix</em> will trigger the rule. - * If BLOCK is specified, requests to that URL will be blocked and fail. - * If REWRITE is specified, the matching prefix will be removed and replaced - * with <em>new-prefix</em>. (If both are specified, BLOCK wins.) Case is - * insensitive for the REWRITE and BLOCK keywords, but sensitive for URLs. - * - * <p>In Gservices, the value of any key that starts with "url:" will be - * interpreted as a rule. The full name of the key is unimportant (but can - * be used to document the intent of the rule, and must be unique). - * Example gservices keys: - * - * <pre> - * url:use_proxy_for_calendar = "http://www.google.com/calendar/ REWRITE http://android.clients.google.com/proxy/calendar/" - * url:stop_crash_reports = "http://android.clients.google.com/crash/ BLOCK" - * url:use_ssl_for_contacts = "http://www.google.com/m8/ REWRITE https://www.google.com/m8/" - * </pre> - */ -public class UrlRules { - public static final String TAG = "UrlRules"; - public static final boolean LOCAL_LOGV = Config.LOGV || false; - - /** Thrown when the rewrite rules can't be parsed. */ - public static class RuleFormatException extends Exception { - public RuleFormatException(String msg) { super(msg); } - } - - /** A single rule specifying actions for URLs matching a certain prefix. */ - public static class Rule implements Comparable { - /** Name assigned to the rule (for logging and debugging). */ - public final String mName; - - /** Prefix required to match this rule. */ - public final String mPrefix; - - /** Text to replace mPrefix with (null to leave alone). */ - public final String mRewrite; - - /** True if matching URLs should be blocked. */ - public final boolean mBlock; - - /** Default rule that does nothing. */ - public static final Rule DEFAULT = new Rule(); - - /** Parse a rewrite rule as given in a Gservices value. */ - public Rule(String name, String rule) throws RuleFormatException { - mName = name; - String[] words = PATTERN_SPACE_PLUS.split(rule); - if (words.length == 0) throw new RuleFormatException("Empty rule"); - - mPrefix = words[0]; - String rewrite = null; - boolean block = false; - for (int pos = 1; pos < words.length; ) { - String word = words[pos].toLowerCase(); - if (word.equals("rewrite") && pos + 1 < words.length) { - rewrite = words[pos + 1]; - pos += 2; - } else if (word.equals("block")) { - block = true; - pos += 1; - } else { - throw new RuleFormatException("Illegal rule: " + rule); - } - // TODO: Parse timeout specifications, etc. - } - - mRewrite = rewrite; - mBlock = block; - } - - /** Create the default Rule. */ - private Rule() { - mName = "DEFAULT"; - mPrefix = ""; - mRewrite = null; - mBlock = false; - } - - /** - * Apply the rule to a particular URL (assumed to match the rule). - * @param url to rewrite or modify. - * @return modified URL, or null if the URL is blocked. - */ - public String apply(String url) { - if (mBlock) { - return null; - } else if (mRewrite != null) { - return mRewrite + url.substring(mPrefix.length()); - } else { - return url; - } - } - - /** More generic rules are greater than more specific rules. */ - public int compareTo(Object o) { - return ((Rule) o).mPrefix.compareTo(mPrefix); - } - } - - /** Cached rule set from Gservices. */ - private static UrlRules sCachedRules = new UrlRules(new Rule[] {}); - - private static final Pattern PATTERN_SPACE_PLUS = Pattern.compile(" +"); - private static final Pattern RULE_PATTERN = Pattern.compile("\\W"); - - /** Gservices digest when sCachedRules was cached. */ - private static String sCachedDigest = null; - - /** Currently active set of Rules. */ - private final Rule[] mRules; - - /** Regular expression with one capturing group for each Rule. */ - private final Pattern mPattern; - - /** - * Create a rewriter from an array of Rules. Normally used only for - * testing. Instead, use {@link #getRules} to get rules from Gservices. - * @param rules to use. - */ - public UrlRules(Rule[] rules) { - // Sort the rules to put the most specific rules first. - Arrays.sort(rules); - - // Construct a regular expression, escaping all the prefix strings. - StringBuilder pattern = new StringBuilder("("); - for (int i = 0; i < rules.length; ++i) { - if (i > 0) pattern.append(")|("); - pattern.append(RULE_PATTERN.matcher(rules[i].mPrefix).replaceAll("\\\\$0")); - } - mPattern = Pattern.compile(pattern.append(")").toString()); - mRules = rules; - } - - /** - * Match a string against every Rule and find one that matches. - * @param uri to match against the Rules in the rewriter. - * @return the most specific matching Rule, or Rule.DEFAULT if none match. - */ - public Rule matchRule(String url) { - Matcher matcher = mPattern.matcher(url); - if (matcher.lookingAt()) { - for (int i = 0; i < mRules.length; ++i) { - if (matcher.group(i + 1) != null) { - return mRules[i]; // Rules are sorted most specific first. - } - } - } - return Rule.DEFAULT; - } - - /** - * Get the (possibly cached) UrlRules based on the rules in Gservices. - * @param resolver to use for accessing the Gservices database. - * @return an updated UrlRules instance - */ - public static synchronized UrlRules getRules(ContentResolver resolver) { - String digest = Settings.Gservices.getString(resolver, - Settings.Gservices.PROVISIONING_DIGEST); - if (sCachedDigest != null && sCachedDigest.equals(digest)) { - // The digest is the same, so the rules are the same. - if (LOCAL_LOGV) Log.v(TAG, "Using cached rules for digest: " + digest); - return sCachedRules; - } - - if (LOCAL_LOGV) Log.v(TAG, "Scanning for Gservices \"url:*\" rules"); - Cursor cursor = resolver.query(Settings.Gservices.CONTENT_URI, - new String[] { - Settings.Gservices.NAME, - Settings.Gservices.VALUE - }, - Settings.Gservices.NAME + " like \"url:%\"", null, - Settings.Gservices.NAME); - try { - ArrayList<Rule> rules = new ArrayList<Rule>(); - while (cursor.moveToNext()) { - try { - String name = cursor.getString(0).substring(4); // "url:X" - String value = cursor.getString(1); - if (value == null || value.length() == 0) continue; - if (LOCAL_LOGV) Log.v(TAG, " Rule " + name + ": " + value); - rules.add(new Rule(name, value)); - } catch (RuleFormatException e) { - // Oops, Gservices has an invalid rule! Skip it. - Log.e(TAG, "Invalid rule from Gservices", e); - Checkin.logEvent(resolver, - Checkin.Events.Tag.GSERVICES_ERROR, e.toString()); - } - } - sCachedRules = new UrlRules(rules.toArray(new Rule[rules.size()])); - sCachedDigest = digest; - if (LOCAL_LOGV) Log.v(TAG, "New rules stored for digest: " + digest); - } finally { - cursor.close(); - } - - return sCachedRules; - } -} diff --git a/core/java/com/google/android/util/SimplePullParser.java b/core/java/com/google/android/util/SimplePullParser.java deleted file mode 100644 index 031790b..0000000 --- a/core/java/com/google/android/util/SimplePullParser.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.util; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.io.Reader; -import java.io.Closeable; - -import android.util.Xml; -import android.util.Log; - -/** - * This is an abstraction of a pull parser that provides several benefits:<ul> - * <li>it is easier to use robustly because it makes it trivial to handle unexpected tags (which - * might have children)</li> - * <li>it makes the handling of text (cdata) blocks more convenient</li> - * <li>it provides convenient methods for getting a mandatory attribute (and throwing an exception - * if it is missing) or an optional attribute (and using a default value if it is missing) - * </ul> - */ -public class SimplePullParser { - public static final String TEXT_TAG = "![CDATA["; - - private String mLogTag = null; - private final XmlPullParser mParser; - private Closeable source; - private String mCurrentStartTag; - - /** - * Constructs a new SimplePullParser to parse the stream - * @param stream stream to parse - * @param encoding the encoding to use - */ - public SimplePullParser(InputStream stream, String encoding) - throws ParseException, IOException { - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(stream, encoding); - moveToStartDocument(parser); - mParser = parser; - mCurrentStartTag = null; - source = stream; - } catch (XmlPullParserException e) { - throw new ParseException(e); - } - } - - /** - * Constructs a new SimplePullParser to parse the xml - * @param parser the underlying parser to use - */ - public SimplePullParser(XmlPullParser parser) { - mParser = parser; - mCurrentStartTag = null; - source = null; - } - - /** - * Constructs a new SimplePullParser to parse the xml - * @param xml the xml to parse - */ - public SimplePullParser(String xml) throws IOException, ParseException { - this(new StringReader(xml)); - } - - /** - * Constructs a new SimplePullParser to parse the xml - * @param reader a reader containing the xml - */ - public SimplePullParser(Reader reader) throws IOException, ParseException { - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(reader); - moveToStartDocument(parser); - mParser = parser; - mCurrentStartTag = null; - source = reader; - } catch (XmlPullParserException e) { - throw new ParseException(e); - } - } - - private static void moveToStartDocument(XmlPullParser parser) - throws XmlPullParserException, IOException { - int eventType; - eventType = parser.getEventType(); - if (eventType != XmlPullParser.START_DOCUMENT) { - throw new XmlPullParserException("Not at start of response"); - } - } - - /** - * Enables logging to the provided log tag. A basic representation of the xml will be logged as - * the xml is parsed. No logging is done unless this is called. - * - * @param logTag the log tag to use when logging - */ - public void setLogTag(String logTag) { - mLogTag = logTag; - } - - /** - * Returns the tag of the next element whose depth is parentDepth plus one - * or null if there are no more such elements before the next start tag. When this returns, - * getDepth() and all methods relating to attributes will refer to the element whose tag is - * returned. - * - * @param parentDepth the depth of the parrent of the item to be returned - * @param textBuilder if null then text blocks will be ignored. If - * non-null then text blocks will be added to the builder and TEXT_TAG - * will be returned when one is found - * @return the next of the next child element's tag, TEXT_TAG if a text block is found, or null - * if there are no more child elements or DATA blocks - * @throws IOException propogated from the underlying parser - * @throws ParseException if there was an error parsing the xml. - */ - public String nextTagOrText(int parentDepth, StringBuilder textBuilder) - throws IOException, ParseException { - while (true) { - int eventType = 0; - try { - eventType = mParser.next(); - } catch (XmlPullParserException e) { - throw new ParseException(e); - } - int depth = mParser.getDepth(); - mCurrentStartTag = null; - - if (eventType == XmlPullParser.START_TAG && depth == parentDepth + 1) { - mCurrentStartTag = mParser.getName(); - if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < depth; i++) sb.append(" "); - sb.append("<").append(mParser.getName()); - int count = mParser.getAttributeCount(); - for (int i = 0; i < count; i++) { - sb.append(" "); - sb.append(mParser.getAttributeName(i)); - sb.append("=\""); - sb.append(mParser.getAttributeValue(i)); - sb.append("\""); - } - sb.append(">"); - Log.d(mLogTag, sb.toString()); - } - return mParser.getName(); - } - - if (eventType == XmlPullParser.END_TAG && depth == parentDepth) { - if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < depth; i++) sb.append(" "); - sb.append("</>"); // Not quite valid xml but it gets the job done. - Log.d(mLogTag, sb.toString()); - } - return null; - } - - if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) { - // we could just rely on the caller calling close(), which it should, but try - // to auto-close for clients that might have missed doing so. - if (source != null) { - source.close(); - source = null; - } - return null; - } - - if (eventType == XmlPullParser.TEXT && depth == parentDepth) { - if (textBuilder == null) { - continue; - } - String text = mParser.getText(); - textBuilder.append(text); - return TEXT_TAG; - } - } - } - - /** - * The same as nextTagOrTexxt(int, StringBuilder) but ignores text blocks. - */ - public String nextTag(int parentDepth) throws IOException, ParseException { - return nextTagOrText(parentDepth, null /* ignore text */); - } - - /** - * Returns the depth of the current element. The depth is 0 before the first - * element has been returned, 1 after that, etc. - * - * @return the depth of the current element - */ - public int getDepth() { - return mParser.getDepth(); - } - - /** - * Consumes the rest of the children, accumulating any text at this level into the builder. - * - * @param textBuilder the builder to contain any text - * @throws IOException propogated from the XmlPullParser - * @throws ParseException if there was an error parsing the xml. - */ - public void readRemainingText(int parentDepth, StringBuilder textBuilder) - throws IOException, ParseException { - while (nextTagOrText(parentDepth, textBuilder) != null) { - } - } - - /** - * Returns the number of attributes on the current element. - * - * @return the number of attributes on the current element - */ - public int numAttributes() { - return mParser.getAttributeCount(); - } - - /** - * Returns the name of the nth attribute on the current element. - * - * @return the name of the nth attribute on the current element - */ - public String getAttributeName(int i) { - return mParser.getAttributeName(i); - } - - /** - * Returns the namespace of the nth attribute on the current element. - * - * @return the namespace of the nth attribute on the current element - */ - public String getAttributeNamespace(int i) { - return mParser.getAttributeNamespace(i); - } - - /** - * Returns the string value of the named attribute. - * - * @param namespace the namespace of the attribute - * @param name the name of the attribute - * @param defaultValue the value to return if the attribute is not specified - * @return the value of the attribute - */ - public String getStringAttribute( - String namespace, String name, String defaultValue) { - String value = mParser.getAttributeValue(namespace, name); - if (null == value) return defaultValue; - return value; - } - - /** - * Returns the string value of the named attribute. An exception will - * be thrown if the attribute is not present. - * - * @param namespace the namespace of the attribute - * @param name the name of the attribute @return the value of the attribute - * @throws ParseException thrown if the attribute is missing - */ - public String getStringAttribute(String namespace, String name) throws ParseException { - String value = mParser.getAttributeValue(namespace, name); - if (null == value) { - throw new ParseException( - "missing '" + name + "' attribute on '" + mCurrentStartTag + "' element"); - } - return value; - } - - /** - * Returns the string value of the named attribute. An exception will - * be thrown if the attribute is not a valid integer. - * - * @param namespace the namespace of the attribute - * @param name the name of the attribute - * @param defaultValue the value to return if the attribute is not specified - * @return the value of the attribute - * @throws ParseException thrown if the attribute not a valid integer. - */ - public int getIntAttribute(String namespace, String name, int defaultValue) - throws ParseException { - String value = mParser.getAttributeValue(namespace, name); - if (null == value) return defaultValue; - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new ParseException("Cannot parse '" + value + "' as an integer"); - } - } - - /** - * Returns the string value of the named attribute. An exception will - * be thrown if the attribute is not present or is not a valid integer. - * - * @param namespace the namespace of the attribute - * @param name the name of the attribute @return the value of the attribute - * @throws ParseException thrown if the attribute is missing or not a valid integer. - */ - public int getIntAttribute(String namespace, String name) - throws ParseException { - String value = getStringAttribute(namespace, name); - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new ParseException("Cannot parse '" + value + "' as an integer"); - } - } - - /** - * Returns the string value of the named attribute. An exception will - * be thrown if the attribute is not a valid long. - * - * @param namespace the namespace of the attribute - * @param name the name of the attribute @return the value of the attribute - * @throws ParseException thrown if the attribute is not a valid long. - */ - public long getLongAttribute(String namespace, String name, long defaultValue) - throws ParseException { - String value = mParser.getAttributeValue(namespace, name); - if (null == value) return defaultValue; - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - throw new ParseException("Cannot parse '" + value + "' as a long"); - } - } - - /** - * Close this SimplePullParser and any underlying resources (e.g., its InputStream or - * Reader source) used by this SimplePullParser. - */ - public void close() { - if (source != null) { - try { - source.close(); - } catch (IOException ioe) { - // ignore - } - } - } - - /** - * Returns the string value of the named attribute. An exception will - * be thrown if the attribute is not present or is not a valid long. - * - * @param namespace the namespace of the attribute - * @param name the name of the attribute @return the value of the attribute - * @throws ParseException thrown if the attribute is missing or not a valid long. - */ - public long getLongAttribute(String namespace, String name) - throws ParseException { - String value = getStringAttribute(namespace, name); - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - throw new ParseException("Cannot parse '" + value + "' as a long"); - } - } - - public static final class ParseException extends Exception { - public ParseException(String message) { - super(message); - } - - public ParseException(String message, Throwable cause) { - super(message, cause); - } - - public ParseException(Throwable cause) { - super(cause); - } - } -} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index c92a86c..67a0bda 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -165,6 +165,7 @@ LOCAL_SHARED_LIBRARIES := \ libEGL \ libGLESv1_CM \ libGLESv2 \ + libETC1 \ libhardware \ libhardware_legacy \ libsonivox \ diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp index 238ece1..7c7bfeb 100644 --- a/core/jni/android/graphics/Typeface.cpp +++ b/core/jni/android/graphics/Typeface.cpp @@ -46,7 +46,7 @@ static SkTypeface* Typeface_createFromTypeface(JNIEnv* env, jobject, SkTypeface* } static void Typeface_unref(JNIEnv* env, jobject obj, SkTypeface* face) { - face->unref(); + SkSafeUnref(face); } static int Typeface_getStyle(JNIEnv* env, jobject obj, SkTypeface* face) { diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index 4041346..589b255 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -23,6 +23,7 @@ #include <dlfcn.h> #include <GLES/gl.h> +#include <ETC1/etc1.h> #include <core/SkBitmap.h> @@ -39,6 +40,7 @@ namespace android { static jclass gIAEClass; static jclass gUOEClass; +static jclass gAIOOBEClass; static inline void mx4transform(float x, float y, float z, float w, const float* pM, float* pDest) { @@ -712,6 +714,297 @@ static jint util_texSubImage2D(JNIEnv *env, jclass clazz, } /* + * ETC1 methods. + */ + +static jclass nioAccessClass; +static jclass bufferClass; +static jmethodID getBasePointerID; +static jmethodID getBaseArrayID; +static jmethodID getBaseArrayOffsetID; +static jfieldID positionID; +static jfieldID limitID; +static jfieldID elementSizeShiftID; + +/* Cache method IDs each time the class is loaded. */ + +static void +nativeClassInitBuffer(JNIEnv *_env) +{ + jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess"); + nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal); + + jclass bufferClassLocal = _env->FindClass("java/nio/Buffer"); + bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal); + + getBasePointerID = _env->GetStaticMethodID(nioAccessClass, + "getBasePointer", "(Ljava/nio/Buffer;)J"); + getBaseArrayID = _env->GetStaticMethodID(nioAccessClass, + "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;"); + getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass, + "getBaseArrayOffset", "(Ljava/nio/Buffer;)I"); + positionID = _env->GetFieldID(bufferClass, "position", "I"); + limitID = _env->GetFieldID(bufferClass, "limit", "I"); + elementSizeShiftID = + _env->GetFieldID(bufferClass, "_elementSizeShift", "I"); +} + +static void * +getPointer(JNIEnv *_env, jobject buffer, jint *remaining) +{ + jint position; + jint limit; + jint elementSizeShift; + jlong pointer; + jint offset; + void *data; + + position = _env->GetIntField(buffer, positionID); + limit = _env->GetIntField(buffer, limitID); + elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID); + *remaining = (limit - position) << elementSizeShift; + pointer = _env->CallStaticLongMethod(nioAccessClass, + getBasePointerID, buffer); + if (pointer != 0L) { + return (void *) (jint) pointer; + } + return NULL; +} + +class BufferHelper { +public: + BufferHelper(JNIEnv *env, jobject buffer) { + mEnv = env; + mBuffer = buffer; + mData = NULL; + mRemaining = 0; + } + + bool checkPointer(const char* errorMessage) { + if (mBuffer) { + mData = getPointer(mEnv, mBuffer, &mRemaining); + if (mData == NULL) { + mEnv->ThrowNew(gIAEClass, errorMessage); + } + return mData != NULL; + } else { + mEnv->ThrowNew(gIAEClass, errorMessage); + return false; + } + } + + inline void* getData() { + return mData; + } + + inline jint remaining() { + return mRemaining; + } + +private: + JNIEnv* mEnv; + jobject mBuffer; + void* mData; + jint mRemaining; +}; + +/** + * Encode a block of pixels. + * + * @param in a pointer to a ETC1_DECODED_BLOCK_SIZE array of bytes that represent a + * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R + * value of pixel (x, y). + * + * @param validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether + * the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing. + * + * @param out an ETC1 compressed version of the data. + * + */ +static void etc1_encodeBlock(JNIEnv *env, jclass clazz, + jobject in, jint validPixelMask, jobject out) { + if (validPixelMask < 0 || validPixelMask > 15) { + env->ThrowNew(gIAEClass, "validPixelMask"); + return; + } + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + if (inB.remaining() < ETC1_DECODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "in's remaining data < DECODED_BLOCK_SIZE"); + } else if (outB.remaining() < ETC1_ENCODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "out's remaining data < ENCODED_BLOCK_SIZE"); + } else { + etc1_encode_block((etc1_byte*) inB.getData(), validPixelMask, + (etc1_byte*) outB.getData()); + } + } +} + +/** + * Decode a block of pixels. + * + * @param in an ETC1 compressed version of the data. + * + * @param out a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a + * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R + * value of pixel (x, y). + */ +static void etc1_decodeBlock(JNIEnv *env, jclass clazz, + jobject in, jobject out){ + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + if (inB.remaining() < ETC1_ENCODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "in's remaining data < ENCODED_BLOCK_SIZE"); + } else if (outB.remaining() < ETC1_DECODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "out's remaining data < DECODED_BLOCK_SIZE"); + } else { + etc1_decode_block((etc1_byte*) inB.getData(), + (etc1_byte*) outB.getData()); + } + } +} + +/** + * Return the size of the encoded image data (does not include size of PKM header). + */ +static jint etc1_getEncodedDataSize(JNIEnv *env, jclass clazz, + jint width, jint height) { + return etc1_get_encoded_data_size(width, height); +} + +/** + * Encode an entire image. + * @param in pointer to the image data. Formatted such that + * pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset; + * @param out pointer to encoded data. Must be large enough to store entire encoded image. + */ +static void etc1_encodeImage(JNIEnv *env, jclass clazz, + jobject in, jint width, jint height, + jint pixelSize, jint stride, jobject out) { + if (pixelSize < 2 || pixelSize > 3) { + env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3"); + return; + } + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + jint imageSize = stride * height; + jint encodedImageSize = etc1_get_encoded_data_size(width, height); + if (inB.remaining() < imageSize) { + env->ThrowNew(gIAEClass, "in's remaining data < image size"); + } else if (outB.remaining() < encodedImageSize) { + env->ThrowNew(gIAEClass, "out's remaining data < encoded image size"); + } else { + int result = etc1_encode_image((etc1_byte*) inB.getData(), + width, height, pixelSize, + stride, + (etc1_byte*) outB.getData()); + } + } +} + +/** + * Decode an entire image. + * @param in the encoded data. + * @param out pointer to the image data. Will be written such that + * pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be + * large enough to store entire image. + */ +static void etc1_decodeImage(JNIEnv *env, jclass clazz, + jobject in, jobject out, + jint width, jint height, + jint pixelSize, jint stride) { + if (pixelSize < 2 || pixelSize > 3) { + env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3"); + return; + } + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + jint imageSize = stride * height; + jint encodedImageSize = etc1_get_encoded_data_size(width, height); + if (inB.remaining() < encodedImageSize) { + env->ThrowNew(gIAEClass, "in's remaining data < encoded image size"); + } else if (outB.remaining() < imageSize) { + env->ThrowNew(gIAEClass, "out's remaining data < image size"); + } else { + int result = etc1_decode_image((etc1_byte*) inB.getData(), + (etc1_byte*) outB.getData(), + width, height, pixelSize, + stride); + } + } +} + +/** + * Format a PKM header + */ +static void etc1_formatHeader(JNIEnv *env, jclass clazz, + jobject header, jint width, jint height) { + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + etc1_pkm_format_header((etc1_byte*) headerB.getData(), width, height); + } + } +} + +/** + * Check if a PKM header is correctly formatted. + */ +static jboolean etc1_isValid(JNIEnv *env, jclass clazz, + jobject header) { + jboolean result = false; + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + result = etc1_pkm_is_valid((etc1_byte*) headerB.getData()); + } + } + return result; +} + +/** + * Read the image width from a PKM header + */ +static jint etc1_getWidth(JNIEnv *env, jclass clazz, + jobject header) { + jint result = 0; + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + result = etc1_pkm_get_width((etc1_byte*) headerB.getData()); + } + } + return result; +} + +/** + * Read the image height from a PKM header + */ +static int etc1_getHeight(JNIEnv *env, jclass clazz, + jobject header) { + jint result = 0; + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + result = etc1_pkm_get_height((etc1_byte*) headerB.getData()); + } + } + return result; +} + +/* * JNI registration */ @@ -721,6 +1014,8 @@ lookupClasses(JNIEnv* env) { env->FindClass("java/lang/IllegalArgumentException")); gUOEClass = (jclass) env->NewGlobalRef( env->FindClass("java/lang/UnsupportedOperationException")); + gAIOOBEClass = (jclass) env->NewGlobalRef( + env->FindClass("java/lang/ArrayIndexOutOfBoundsException")); } static JNINativeMethod gMatrixMethods[] = { @@ -742,6 +1037,18 @@ static JNINativeMethod gUtilsMethods[] = { { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D }, }; +static JNINativeMethod gEtc1Methods[] = { + { "encodeBlock", "(Ljava/nio/Buffer;ILjava/nio/Buffer;)V", (void*) etc1_encodeBlock }, + { "decodeBlock", "(Ljava/nio/Buffer;Ljava/nio/Buffer;)V", (void*) etc1_decodeBlock }, + { "getEncodedDataSize", "(II)I", (void*) etc1_getEncodedDataSize }, + { "encodeImage", "(Ljava/nio/Buffer;IIIILjava/nio/Buffer;)V", (void*) etc1_encodeImage }, + { "decodeImage", "(Ljava/nio/Buffer;Ljava/nio/Buffer;IIII)V", (void*) etc1_decodeImage }, + { "formatHeader", "(Ljava/nio/Buffer;II)V", (void*) etc1_formatHeader }, + { "isValid", "(Ljava/nio/Buffer;)Z", (void*) etc1_isValid }, + { "getWidth", "(Ljava/nio/Buffer;)I", (void*) etc1_getWidth }, + { "getHeight", "(Ljava/nio/Buffer;)I", (void*) etc1_getHeight }, +}; + typedef struct _ClassRegistrationInfo { const char* classPath; JNINativeMethod* methods; @@ -752,11 +1059,13 @@ static ClassRegistrationInfo gClasses[] = { {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)}, {"android/opengl/Visibility", gVisiblityMethods, NELEM(gVisiblityMethods)}, {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)}, + {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)}, }; int register_android_opengl_classes(JNIEnv* env) { lookupClasses(env); + nativeClassInitBuffer(env); int result = 0; for (int i = 0; i < NELEM(gClasses); i++) { ClassRegistrationInfo* cri = &gClasses[i]; diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index d57e526..11463a2 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -220,7 +220,7 @@ void JNICameraContext::postData(int32_t msgType, const sp<IMemory>& dataPtr) break; default: // TODO: Change to LOGV - LOGD("dataCallback(%d, %p)", msgType, dataPtr.get()); + LOGV("dataCallback(%d, %p)", msgType, dataPtr.get()); copyAndPost(env, dataPtr, msgType); break; } diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp index 1ae3ec7..ee8d836 100644 --- a/core/jni/android_os_MemoryFile.cpp +++ b/core/jni/android_os_MemoryFile.cpp @@ -30,8 +30,6 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na { const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL); - // round up length to page boundary - length = (((length - 1) / getpagesize()) + 1) * getpagesize(); int result = ashmem_create_region(namestr, length); if (name) @@ -118,7 +116,7 @@ static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe } } -static jint android_os_MemoryFile_get_mapped_size(JNIEnv* env, jobject clazz, +static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz, jobject fileDescriptor) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region. @@ -146,8 +144,8 @@ static const JNINativeMethod methods[] = { {"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, - {"native_get_mapped_size", "(Ljava/io/FileDescriptor;)I", - (void*)android_os_MemoryFile_get_mapped_size} + {"native_get_size", "(Ljava/io/FileDescriptor;)I", + (void*)android_os_MemoryFile_get_size} }; static const char* const kClassPathName = "android/os/MemoryFile"; diff --git a/core/jni/android_server_BluetoothA2dpService.cpp b/core/jni/android_server_BluetoothA2dpService.cpp index 7a3bbbb..4eab4b3 100644 --- a/core/jni/android_server_BluetoothA2dpService.cpp +++ b/core/jni/android_server_BluetoothA2dpService.cpp @@ -38,6 +38,7 @@ namespace android { #ifdef HAVE_BLUETOOTH static jmethodID method_onSinkPropertyChanged; +static jmethodID method_onConnectSinkResult; typedef struct { JavaVM *vm; @@ -47,6 +48,7 @@ typedef struct { } native_data_t; static native_data_t *nat = NULL; // global native data +static void onConnectSinkResult(DBusMessage *msg, void *user, void *n); static Properties sink_properties[] = { {"State", DBUS_TYPE_STRING}, @@ -133,9 +135,12 @@ static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) { LOGV(__FUNCTION__); if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); + int len = env->GetStringLength(path) + 1; + char *context_path = (char *)calloc(len, sizeof(char)); + strlcpy(context_path, c_path, len); // for callback - bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat, - c_path, "org.bluez.AudioSink", "Connect", + bool ret = dbus_func_args_async(env, nat->conn, -1, onConnectSinkResult, context_path, + nat, c_path, "org.bluez.AudioSink", "Connect", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); @@ -237,6 +242,31 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { return result; } + +void onConnectSinkResult(DBusMessage *msg, void *user, void *n) { + LOGV(__FUNCTION__); + + native_data_t *nat = (native_data_t *)n; + const char *path = (const char *)user; + DBusError err; + dbus_error_init(&err); + JNIEnv *env; + nat->vm->GetEnv((void**)&env, nat->envVer); + + + bool result = JNI_TRUE; + if (dbus_set_error_from_message(&err, msg)) { + LOG_AND_FREE_DBUS_ERROR(&err); + result = JNI_FALSE; + } + LOGV("... Device Path = %s, result = %d", path, result); + env->CallVoidMethod(nat->me, + method_onConnectSinkResult, + env->NewStringUTF(path), + result); + free(user); +} + #endif @@ -244,7 +274,7 @@ static JNINativeMethod sMethods[] = { {"initNative", "()Z", (void *)initNative}, {"cleanupNative", "()V", (void *)cleanupNative}, - /* Bluez audio 4.40 API */ + /* Bluez audio 4.47 API */ {"connectSinkNative", "(Ljava/lang/String;)Z", (void *)connectSinkNative}, {"disconnectSinkNative", "(Ljava/lang/String;)Z", (void *)disconnectSinkNative}, {"suspendSinkNative", "(Ljava/lang/String;)Z", (void*)suspendSinkNative}, @@ -263,6 +293,8 @@ int register_android_server_BluetoothA2dpService(JNIEnv *env) { #ifdef HAVE_BLUETOOTH method_onSinkPropertyChanged = env->GetMethodID(clazz, "onSinkPropertyChanged", "(Ljava/lang/String;[Ljava/lang/String;)V"); + method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult", + "(Ljava/lang/String;Z)V"); #endif return AndroidRuntime::registerNativeMethods(env, diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp index fde6ca6..d89a7e6 100644 --- a/core/jni/android_text_format_Time.cpp +++ b/core/jni/android_text_format_Time.cpp @@ -382,7 +382,7 @@ static bool check_char(JNIEnv* env, const jchar *s, int spos, jchar expected) jchar c = s[spos]; if (c != expected) { char msg[100]; - sprintf(msg, "Unexpected %c at pos=%d. Expected %c.", c, spos, + sprintf(msg, "Unexpected character 0x%02x at pos=%d. Expected %c.", c, spos, expected); jniThrowException(env, "android/util/TimeFormatException", msg); return false; @@ -483,6 +483,12 @@ static jboolean android_text_format_Time_parse3339(JNIEnv* env, int n; jboolean inUtc = false; + if (len < 10) { + jniThrowException(env, "android/util/TimeFormatException", + "Time input is too short; must be at least 10 characters"); + return false; + } + // year n = get_char(env, s, 0, 1000, &thrown); n += get_char(env, s, 1, 100, &thrown); @@ -510,7 +516,7 @@ static jboolean android_text_format_Time_parse3339(JNIEnv* env, if (thrown) return false; env->SetIntField(This, g_mdayField, n); - if (len >= 17) { + if (len >= 19) { // T if (!check_char(env, s, 10, 'T')) return false; @@ -541,10 +547,19 @@ static jboolean android_text_format_Time_parse3339(JNIEnv* env, if (thrown) return false; env->SetIntField(This, g_secField, n); - // skip the '.XYZ' -- we don't care about subsecond precision. + // skip the '.XYZ' -- we don't care about subsecond precision. + int tz_index = 19; + if (tz_index < len && s[tz_index] == '.') { + do { + tz_index++; + } while (tz_index < len + && s[tz_index] >= '0' + && s[tz_index] <= '9'); + } + int offset = 0; - if (len >= 23) { - char c = s[23]; + if (len > tz_index) { + char c = s[tz_index]; // NOTE: the offset is meant to be subtracted to get from local time // to UTC. we therefore use 1 for '-' and -1 for '+'. @@ -561,27 +576,34 @@ static jboolean android_text_format_Time_parse3339(JNIEnv* env, break; default: char msg[100]; - sprintf(msg, "Unexpected %c at position 19. Expected + or -", - c); + sprintf(msg, "Unexpected character 0x%02x at position %d. Expected + or -", + c, tz_index); jniThrowException(env, "android/util/TimeFormatException", msg); return false; } inUtc = true; if (offset != 0) { + if (len < tz_index + 5) { + char msg[100]; + sprintf(msg, "Unexpected length; should be %d characters", tz_index + 5); + jniThrowException(env, "android/util/TimeFormatException", msg); + return false; + } + // hour - n = get_char(env, s, 24, 10, &thrown); - n += get_char(env, s, 25, 1, &thrown); + n = get_char(env, s, tz_index + 1, 10, &thrown); + n += get_char(env, s, tz_index + 2, 1, &thrown); if (thrown) return false; n *= offset; hour += n; // : - if (!check_char(env, s, 26, ':')) return false; + if (!check_char(env, s, tz_index + 3, ':')) return false; // minute - n = get_char(env, s, 27, 10, &thrown); - n += get_char(env, s, 28, 1, &thrown); + n = get_char(env, s, tz_index + 4, 10, &thrown); + n += get_char(env, s, tz_index + 5, 1, &thrown); if (thrown) return false; n *= offset; minute += n; diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index e83d2e2..2fff727 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -524,7 +524,6 @@ static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject } for (int i=0; i<N; i++) { - LOGD("locale %2d: '%s'", i, locales[i].string()); env->SetObjectArrayElement(result, i, env->NewStringUTF(locales[i].string())); } diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp index 34b7c89..78356cf 100644 --- a/core/jni/android_util_EventLog.cpp +++ b/core/jni/android_util_EventLog.cpp @@ -21,13 +21,6 @@ #include "jni.h" #include "cutils/logger.h" -#define END_DELIMITER '\n' -#define INT_BUFFER_SIZE (sizeof(jbyte)+sizeof(jint)+sizeof(END_DELIMITER)) -#define LONG_BUFFER_SIZE (sizeof(jbyte)+sizeof(jlong)+sizeof(END_DELIMITER)) -#define INITAL_BUFFER_CAPACITY 256 - -#define MAX(a,b) ((a>b)?a:b) - namespace android { static jclass gCollectionClass; @@ -47,107 +40,6 @@ static jfieldID gLongValueID; static jclass gStringClass; -struct ByteBuf { - size_t len; - size_t capacity; - uint8_t* buf; - - ByteBuf(size_t initSize) { - buf = (uint8_t*)malloc(initSize); - len = 0; - capacity = initSize; - } - - ~ByteBuf() { - free(buf); - } - - bool ensureExtraCapacity(size_t extra) { - size_t spaceNeeded = len + extra; - if (spaceNeeded > capacity) { - size_t newCapacity = MAX(spaceNeeded, 2 * capacity); - void* newBuf = realloc(buf, newCapacity); - if (newBuf == NULL) { - return false; - } - capacity = newCapacity; - buf = (uint8_t*)newBuf; - return true; - } else { - return true; - } - } - - void putIntEvent(jint value) { - bool succeeded = ensureExtraCapacity(INT_BUFFER_SIZE); - buf[len++] = EVENT_TYPE_INT; - memcpy(buf+len, &value, sizeof(jint)); - len += sizeof(jint); - } - - void putByte(uint8_t value) { - bool succeeded = ensureExtraCapacity(sizeof(uint8_t)); - buf[len++] = value; - } - - void putLongEvent(jlong value) { - bool succeeded = ensureExtraCapacity(LONG_BUFFER_SIZE); - buf[len++] = EVENT_TYPE_LONG; - memcpy(buf+len, &value, sizeof(jlong)); - len += sizeof(jlong); - } - - - void putStringEvent(JNIEnv* env, jstring value) { - const char* strValue = env->GetStringUTFChars(value, NULL); - uint32_t strLen = strlen(strValue); //env->GetStringUTFLength(value); - bool succeeded = ensureExtraCapacity(1 + sizeof(uint32_t) + strLen); - buf[len++] = EVENT_TYPE_STRING; - memcpy(buf+len, &strLen, sizeof(uint32_t)); - len += sizeof(uint32_t); - memcpy(buf+len, strValue, strLen); - env->ReleaseStringUTFChars(value, strValue); - len += strLen; - } - - void putList(JNIEnv* env, jobject list) { - jobjectArray items = (jobjectArray) env->GetObjectField(list, gListItemsID); - if (items == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return; - } - - jsize numItems = env->GetArrayLength(items); - putByte(EVENT_TYPE_LIST); - putByte(numItems); - // We'd like to call GetPrimitveArrayCritical() but that might - // not be safe since we're going to be doing some I/O - for (int i = 0; i < numItems; i++) { - jobject item = env->GetObjectArrayElement(items, i); - if (env->IsInstanceOf(item, gIntegerClass)) { - jint intVal = env->GetIntField(item, gIntegerValueID); - putIntEvent(intVal); - } else if (env->IsInstanceOf(item, gLongClass)) { - jlong longVal = env->GetLongField(item, gLongValueID); - putLongEvent(longVal); - } else if (env->IsInstanceOf(item, gStringClass)) { - putStringEvent(env, (jstring)item); - } else if (env->IsInstanceOf(item, gListClass)) { - putList(env, item); - } else { - jniThrowException( - env, - "java/lang/IllegalArgumentException", - "Attempt to log an illegal item type."); - return; - } - env->DeleteLocalRef(item); - } - - env->DeleteLocalRef(items); - } -}; - /* * In class android.util.EventLog: * static native int writeEvent(int tag, int value) @@ -170,41 +62,80 @@ static jint android_util_EventLog_writeEvent_Long(JNIEnv* env, jobject clazz, /* * In class android.util.EventLog: - * static native int writeEvent(long tag, List value) + * static native int writeEvent(int tag, String value) */ -static jint android_util_EventLog_writeEvent_List(JNIEnv* env, jobject clazz, - jint tag, jobject value) { - if (value == NULL) { - jclass clazz = env->FindClass("java/lang/IllegalArgumentException"); - env->ThrowNew(clazz, "writeEvent needs a value."); - return -1; - } - ByteBuf byteBuf(INITAL_BUFFER_CAPACITY); - byteBuf.putList(env, value); - byteBuf.putByte((uint8_t)END_DELIMITER); - int numBytesPut = byteBuf.len; - int bytesWritten = android_bWriteLog(tag, byteBuf.buf, numBytesPut); - return bytesWritten; +static jint android_util_EventLog_writeEvent_String(JNIEnv* env, jobject clazz, + jint tag, jstring value) { + uint8_t buf[LOGGER_ENTRY_MAX_PAYLOAD]; + + // Don't throw NPE -- I feel like it's sort of mean for a logging function + // to be all crashy if you pass in NULL -- but make the NULL value explicit. + const char *str = value != NULL ? env->GetStringUTFChars(value, NULL) : "NULL"; + jint len = strlen(str); + const int max = sizeof(buf) - sizeof(len) - 2; // Type byte, final newline + if (len > max) len = max; + + buf[0] = EVENT_TYPE_STRING; + memcpy(&buf[1], &len, sizeof(len)); + memcpy(&buf[1 + sizeof(len)], str, len); + buf[1 + sizeof(len) + len] = '\n'; + + if (value != NULL) env->ReleaseStringUTFChars(value, str); + return android_bWriteLog(tag, buf, 2 + sizeof(len) + len); } /* * In class android.util.EventLog: - * static native int writeEvent(int tag, String value) + * static native int writeEvent(long tag, Object... value) */ -static jint android_util_EventLog_writeEvent_String(JNIEnv* env, jobject clazz, - jint tag, jstring value) { +static jint android_util_EventLog_writeEvent_Array(JNIEnv* env, jobject clazz, + jint tag, jobjectArray value) { if (value == NULL) { - jclass clazz = env->FindClass("java/lang/IllegalArgumentException"); - env->ThrowNew(clazz, "logEvent needs a value."); - return -1; + return android_util_EventLog_writeEvent_String(env, clazz, tag, NULL); + } + + uint8_t buf[LOGGER_ENTRY_MAX_PAYLOAD]; + const size_t max = sizeof(buf) - 1; // leave room for final newline + size_t pos = 2; // Save room for type tag & array count + + jsize copied = 0, num = env->GetArrayLength(value); + for (; copied < num && copied < 256; ++copied) { + jobject item = env->GetObjectArrayElement(value, copied); + if (item == NULL || env->IsInstanceOf(item, gStringClass)) { + if (pos + 1 + sizeof(jint) > max) break; + const char *str = item != NULL ? env->GetStringUTFChars((jstring) item, NULL) : "NULL"; + jint len = strlen(str); + if (pos + 1 + sizeof(len) + len > max) len = max - pos - 1 - sizeof(len); + buf[pos++] = EVENT_TYPE_STRING; + memcpy(&buf[pos], &len, sizeof(len)); + memcpy(&buf[pos + sizeof(len)], str, len); + pos += sizeof(len) + len; + if (item != NULL) env->ReleaseStringUTFChars((jstring) item, str); + } else if (env->IsInstanceOf(item, gIntegerClass)) { + jint intVal = env->GetIntField(item, gIntegerValueID); + if (pos + 1 + sizeof(intVal) > max) break; + buf[pos++] = EVENT_TYPE_INT; + memcpy(&buf[pos], &intVal, sizeof(intVal)); + pos += sizeof(intVal); + } else if (env->IsInstanceOf(item, gLongClass)) { + jlong longVal = env->GetLongField(item, gLongValueID); + if (pos + 1 + sizeof(longVal) > max) break; + buf[pos++] = EVENT_TYPE_LONG; + memcpy(&buf[pos], &longVal, sizeof(longVal)); + pos += sizeof(longVal); + } else { + jniThrowException(env, + "java/lang/IllegalArgumentException", + "Invalid payload item type"); + return -1; + } + env->DeleteLocalRef(item); } - ByteBuf byteBuf(INITAL_BUFFER_CAPACITY); - byteBuf.putStringEvent(env, value); - byteBuf.putByte((uint8_t)END_DELIMITER); - int numBytesPut = byteBuf.len; - int bytesWritten = android_bWriteLog(tag, byteBuf.buf, numBytesPut); - return bytesWritten; + buf[0] = EVENT_TYPE_LIST; + buf[1] = copied; + buf[pos++] = '\n'; + return android_bWriteLog(tag, buf, pos); } /* @@ -276,81 +207,6 @@ static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz, } /* - * In class android.util.EventLog: - * static native void readEvents(String path, Collection<Event> output) - * - * Reads events from a file (See Checkin.Aggregation). Events are stored in - * native raw format (logger_entry + payload). - */ -static void android_util_EventLog_readEventsFile(JNIEnv* env, jobject clazz, jstring path, - jobject out) { - if (path == NULL || out == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return; - } - - const char *pathString = env->GetStringUTFChars(path, 0); - int fd = open(pathString, O_RDONLY | O_NONBLOCK); - env->ReleaseStringUTFChars(path, pathString); - - if (fd < 0) { - jniThrowIOException(env, errno); - return; - } - - uint8_t buf[LOGGER_ENTRY_MAX_LEN]; - for (;;) { - // read log entry structure from file - int len = read(fd, buf, sizeof(logger_entry)); - if (len == 0) { - break; // end of file - } else if (len < 0) { - jniThrowIOException(env, errno); - } else if ((size_t) len < sizeof(logger_entry)) { - jniThrowException(env, "java/io/IOException", "Event header too short"); - break; - } - - // read event payload - logger_entry* entry = (logger_entry*) buf; - if (entry->len > LOGGER_ENTRY_MAX_PAYLOAD) { - jniThrowException(env, - "java/lang/IllegalArgumentException", - "Too much data for event payload. Corrupt file?"); - break; - } - - len = read(fd, buf + sizeof(logger_entry), entry->len); - if (len == 0) { - break; // end of file - } else if (len < 0) { - jniThrowIOException(env, errno); - } else if ((size_t) len < entry->len) { - jniThrowException(env, "java/io/IOException", "Event payload too short"); - break; - } - - // create EventLog$Event and add it to the collection - int buffer_size = sizeof(logger_entry) + entry->len; - jbyteArray array = env->NewByteArray(buffer_size); - if (array == NULL) break; - - jbyte *bytes = env->GetByteArrayElements(array, NULL); - memcpy(bytes, buf, buffer_size); - env->ReleaseByteArrayElements(array, bytes, 0); - - jobject event = env->NewObject(gEventClass, gEventInitID, array); - if (event == NULL) break; - - env->CallBooleanMethod(out, gCollectionAddID, event); - env->DeleteLocalRef(event); - env->DeleteLocalRef(array); - } - - close(fd); -} - -/* * JNI registration. */ static JNINativeMethod gRegisterMethods[] = { @@ -362,22 +218,17 @@ static JNINativeMethod gRegisterMethods[] = { (void*) android_util_EventLog_writeEvent_String }, { "writeEvent", - "(ILandroid/util/EventLog$List;)I", - (void*) android_util_EventLog_writeEvent_List + "(I[Ljava/lang/Object;)I", + (void*) android_util_EventLog_writeEvent_Array }, { "readEvents", "([ILjava/util/Collection;)V", (void*) android_util_EventLog_readEvents }, - { "readEvents", - "(Ljava/lang/String;Ljava/util/Collection;)V", - (void*) android_util_EventLog_readEventsFile - } }; static struct { const char *name; jclass *clazz; } gClasses[] = { { "android/util/EventLog$Event", &gEventClass }, - { "android/util/EventLog$List", &gListClass }, { "java/lang/Integer", &gIntegerClass }, { "java/lang/Long", &gLongClass }, { "java/lang/String", &gStringClass }, @@ -386,7 +237,6 @@ static struct { const char *name; jclass *clazz; } gClasses[] = { static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = { { &gIntegerClass, "value", "I", &gIntegerValueID }, - { &gListClass, "mItems", "[Ljava/lang/Object;", &gListItemsID }, { &gLongClass, "value", "J", &gLongValueID }, }; @@ -430,4 +280,3 @@ int register_android_util_EventLog(JNIEnv* env) { } }; // namespace android - diff --git a/core/res/Android.mk b/core/res/Android.mk index 78cb86d..7d11148 100644 --- a/core/res/Android.mk +++ b/core/res/Android.mk @@ -24,7 +24,7 @@ LOCAL_CERTIFICATE := platform # since these resources will be used by many apps. LOCAL_AAPT_FLAGS := -x -LOCAL_MODULE_TAGS := user +LOCAL_MODULE_TAGS := optional # Install this alongside the libraries. LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4a7adcc..c49a86a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -403,7 +403,13 @@ android:label="@string/permlab_recordAudio" android:description="@string/permdesc_recordAudio" /> - <!-- Required to be able to access the camera device. --> + <!-- Required to be able to access the camera device. + <p>This will automatically enforce the <a + href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code + <uses-feature>}</a> manifest element for <em>all</em> camera features. + If you do not require all camera features or can properly operate if a camera + is not available, then you must modify your manifest as appropriate in order to + install on devices that don't support all camera features.</p> --> <permission android:name="android.permission.CAMERA" android:permissionGroup="android.permission-group.HARDWARE_CONTROLS" android:protectionLevel="dangerous" @@ -550,12 +556,30 @@ android:label="@string/permlab_changeConfiguration" android:description="@string/permdesc_changeConfiguration" /> - <!-- Allows an application to restart other applications. --> + <!-- @deprecated The {@link android.app.ActivityManager#restartPackage} + API is no longer supported. --> <permission android:name="android.permission.RESTART_PACKAGES" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" - android:protectionLevel="dangerous" - android:label="@string/permlab_restartPackages" - android:description="@string/permdesc_restartPackages" /> + android:protectionLevel="normal" + android:label="@string/permlab_killBackgroundProcesses" + android:description="@string/permdesc_killBackgroundProcesses" /> + + <!-- Allows an application to call + {@link android.app.ActivityManager#killBackgroundProcesses}. --> + <permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" + android:permissionGroup="android.permission-group.SYSTEM_TOOLS" + android:protectionLevel="normal" + android:label="@string/permlab_killBackgroundProcesses" + android:description="@string/permdesc_killBackgroundProcesses" /> + + <!-- Allows an application to call + {@link android.app.ActivityManager#forceStopPackage}. + @hide --> + <permission android:name="android.permission.FORCE_STOP_PACKAGES" + android:permissionGroup="android.permission-group.SYSTEM_TOOLS" + android:protectionLevel="signature" + android:label="@string/permlab_forceStopPackages" + android:description="@string/permdesc_forceStopPackages" /> <!-- Allows an application to retrieve state dump information from system services. --> @@ -1126,6 +1150,12 @@ android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:protectionLevel="signatureOrSystem" /> + <!-- Allow an application to read and write the cache partition. --> + <permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" + android:label="@string/permlab_cache_filesystem" + android:description="@string/permdesc_cache_filesystem" + android:protectionLevel="signatureOrSystem" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/res/anim/lock_screen_enter.xml b/core/res/res/anim/lock_screen_enter.xml new file mode 100644 index 0000000..dd47ff8 --- /dev/null +++ b/core/res/res/anim/lock_screen_enter.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@anim/decelerate_interpolator"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_mediumAnimTime" /> +</set> diff --git a/core/res/res/anim/lock_screen_exit.xml b/core/res/res/anim/lock_screen_exit.xml index 58bc6db..077fc6b 100644 --- a/core/res/res/anim/lock_screen_exit.xml +++ b/core/res/res/anim/lock_screen_exit.xml @@ -17,7 +17,8 @@ */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator"> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@anim/accelerate_interpolator"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="@android:integer/config_longAnimTime" /> </set> diff --git a/core/res/res/drawable-hdpi/pickerbox_background.png b/core/res/res/drawable-hdpi/pickerbox_background.png Binary files differdeleted file mode 100644 index 9315a31..0000000 --- a/core/res/res/drawable-hdpi/pickerbox_background.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/pickerbox_selected.9.png b/core/res/res/drawable-hdpi/pickerbox_selected.9.png Binary files differdeleted file mode 100644 index a88ec63..0000000 --- a/core/res/res/drawable-hdpi/pickerbox_selected.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/pickerbox_unselected.9.png b/core/res/res/drawable-hdpi/pickerbox_unselected.9.png Binary files differdeleted file mode 100644 index 9f6b7cb..0000000 --- a/core/res/res/drawable-hdpi/pickerbox_unselected.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/pickerbox_background.png b/core/res/res/drawable-mdpi/pickerbox_background.png Binary files differdeleted file mode 100644 index 6494cd8..0000000 --- a/core/res/res/drawable-mdpi/pickerbox_background.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/pickerbox_selected.9.png b/core/res/res/drawable-mdpi/pickerbox_selected.9.png Binary files differdeleted file mode 100644 index d986a31..0000000 --- a/core/res/res/drawable-mdpi/pickerbox_selected.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/pickerbox_unselected.9.png b/core/res/res/drawable-mdpi/pickerbox_unselected.9.png Binary files differdeleted file mode 100644 index 27ec6b9..0000000 --- a/core/res/res/drawable-mdpi/pickerbox_unselected.9.png +++ /dev/null diff --git a/core/res/res/drawable/pickerbox.xml b/core/res/res/drawable/pickerbox.xml deleted file mode 100644 index 9cb2436..0000000 --- a/core/res/res/drawable/pickerbox.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/pickerbox_selected" /> - <item android:drawable="@drawable/pickerbox_unselected" /> -</selector> - diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml index 45e96a3..2a23ada 100644 --- a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml +++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml @@ -16,59 +16,55 @@ ** limitations under the License. */ --> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" + android:orientation="vertical" android:background="@android:color/background_dark" - > + android:gravity="center_horizontal"> <LinearLayout android:id="@+id/topDisplayGroup" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:orientation="vertical" - > + android:orientation="vertical"> <!-- header text ('Enter Pin Code') --> <TextView android:id="@+id/headerText" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginTop="9dip" android:gravity="center" android:lines="2" - android:textAppearance="?android:attr/textAppearanceLarge" - /> + android:textAppearance="?android:attr/textAppearanceLarge"/> - <RelativeLayout + <!-- password entry --> + <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginTop="18dip" + android:orientation="horizontal" android:layout_marginRight="6dip" android:layout_marginLeft="6dip" - android:background="@android:drawable/edit_text" - > + android:gravity="center_vertical" + android:background="@android:drawable/edit_text"> <!-- displays dots as user enters pin --> <TextView android:id="@+id/pinDisplay" - android:layout_width="wrap_content" - android:layout_height="64dip" - android:layout_centerInParent="true" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" android:maxLines="1" android:textAppearance="?android:attr/textAppearanceLargeInverse" android:textStyle="bold" android:inputType="textPassword" - /> + /> <ImageButton android:id="@+id/backspace" android:src="@android:drawable/ic_input_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_centerVertical="true" - android:layout_marginRight="1dip" - /> - </RelativeLayout> - + android:layout_marginRight="-3dip" + android:layout_marginBottom="-3dip" + /> + </LinearLayout> </LinearLayout> @@ -78,16 +74,14 @@ android:layout_height="wrap_content" android:layout_below="@id/topDisplayGroup" android:layout_marginTop="10dip" - android:orientation="vertical" - > + android:orientation="vertical"> <LinearLayout android:layout_width="fill_parent" android:layout_height="64dip" android:layout_marginLeft="2dip" android:layout_marginRight="2dip" - android:orientation="horizontal" - > + android:orientation="horizontal"> <Button android:id="@+id/one" android:layout_width="0sp" @@ -125,8 +119,7 @@ android:layout_height="64dip" android:layout_marginLeft="2dip" android:layout_marginRight="2dip" - android:orientation="horizontal" - > + android:orientation="horizontal"> <Button android:id="@+id/four" android:layout_width="0sp" @@ -164,8 +157,7 @@ android:layout_height="64dip" android:layout_marginLeft="2dip" android:layout_marginRight="2dip" - android:orientation="horizontal" - > + android:orientation="horizontal"> <Button android:id="@+id/seven" android:layout_width="0sp" @@ -203,8 +195,7 @@ android:layout_height="64dip" android:layout_marginLeft="2dip" android:layout_marginRight="2dip" - android:orientation="horizontal" - > + android:orientation="horizontal"> <Button android:id="@+id/ok" android:layout_width="0sp" @@ -242,27 +233,33 @@ <!-- end keypad --> </LinearLayout> - - <!-- emergency call button --> - <Button - android:id="@+id/emergencyCall" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_alignParentBottom="true" - android:drawableLeft="@android:drawable/ic_emergency" - android:drawablePadding="8dip" - android:text="@android:string/lockscreen_emergency_call" - /> - <!-- spacer below keypad --> <View android:id="@+id/spacerBottom" android:layout_width="fill_parent" android:layout_height="1dip" - android:layout_marginBottom="6dip" + android:layout_marginTop="6dip" android:layout_above="@id/emergencyCall" - android:background="@android:drawable/divider_horizontal_dark"/> + android:background="@android:drawable/divider_horizontal_dark" + /> + <!-- The emergency button should take the rest of the space and be centered vertically --> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical"> + + <!-- emergency call button --> + <Button + android:id="@+id/emergencyCall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:drawableLeft="@android:drawable/ic_emergency" + android:drawablePadding="8dip" + android:text="@android:string/lockscreen_emergency_call" + /> + </LinearLayout> -</RelativeLayout> +</LinearLayout> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index bc354c5..047115c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -462,10 +462,17 @@ size.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_restartPackages">restart other applications</string> + <string name="permlab_killBackgroundProcesses">kill background processes</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_restartPackages">Allows an application to - forcibly restart other applications.</string> + <string name="permdesc_killBackgroundProcesses">Allows an application to + kill background processes of other applications, even if memory + isn\'t low.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_forceStopPackages">force stop other applications</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_forceStopPackages">Allows an application to + forcibly stop other applications.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_forceBack">force application to close</string> @@ -1139,6 +1146,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_sdcardWrite">Allows an application to write to the SD card.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_cache_filesystem">access the cache filesystem</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_cache_filesystem">Allows an application to read and write the cache filesystem.</string> + <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip /> <!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. --> <string-array name="phoneTypes"> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index b155769..f2b52d9 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -101,6 +101,7 @@ <!-- Standard animations for a non-full-screen window or activity. --> <style name="Animation.LockScreen"> + <item name="windowEnterAnimation">@anim/lock_screen_enter</item> <item name="windowExitAnimation">@anim/lock_screen_exit</item> </style> |