diff options
Diffstat (limited to 'core/java/android')
73 files changed, 4871 insertions, 708 deletions
diff --git a/core/java/android/accounts/AccountManagerResponse.java b/core/java/android/accounts/AccountManagerResponse.java index 1cd6a74..11c6e60 100644 --- a/core/java/android/accounts/AccountManagerResponse.java +++ b/core/java/android/accounts/AccountManagerResponse.java @@ -22,7 +22,8 @@ import android.os.Parcelable; import android.os.RemoteException; /** - * Used by Account Authenticators to return a response. + * Used to return a response to the AccountManager. + * @hide */ public class AccountManagerResponse implements Parcelable { private IAccountManagerResponse mResponse; diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 800ad749..440668f 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -152,8 +152,7 @@ public class AccountManagerService new AtomicReference<AccountManagerService>(); private static final boolean isDebuggableMonkeyBuild = - SystemProperties.getBoolean("ro.monkey", false) - && SystemProperties.getBoolean("ro.debuggable", false); + SystemProperties.getBoolean("ro.monkey", false); private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; static { diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java index e3ed2e9..4282c1b 100644 --- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java +++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java @@ -26,6 +26,7 @@ import android.view.ViewGroup; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.text.TextUtils; import com.android.internal.R; /** @@ -59,7 +60,7 @@ public class GrantCredentialsPermissionActivity extends Activity implements View TextView messageView = (TextView) getWindow().findViewById(R.id.message); String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL); - if (authTokenLabel.length() == 0) { + if (TextUtils.isEmpty(authTokenLabel)) { CharSequence grantCredentialsPermissionFormat = getResources().getText( R.string.grant_credentials_permission_message_desc); messageView.setText(String.format(grantCredentialsPermissionFormat.toString(), diff --git a/core/java/android/annotation/SdkConstant.java b/core/java/android/annotation/SdkConstant.java index 6ac70f0..0a53186 100644 --- a/core/java/android/annotation/SdkConstant.java +++ b/core/java/android/annotation/SdkConstant.java @@ -29,7 +29,7 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.SOURCE) public @interface SdkConstant { public static enum SdkConstantType { - ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY; + ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE; } SdkConstantType value(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b116bf8..909620d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -178,15 +178,16 @@ public final class ActivityThread { * null. */ Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { + ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + Resources r; synchronized (mPackages) { // Resources is app scale dependent. - ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); if (false) { Log.w(TAG, "getTopLevelResources: " + resDir + " / " + compInfo.applicationScale); } WeakReference<Resources> wr = mActiveResources.get(key); - Resources r = wr != null ? wr.get() : null; + r = wr != null ? wr.get() : null; if (r != null && r.getAssets().isUpToDate()) { if (false) { Log.w(TAG, "Returning cached resources " + r + " " + resDir @@ -194,25 +195,37 @@ public final class ActivityThread { } return r; } + } - //if (r != null) { - // Log.w(TAG, "Throwing away out-of-date resources!!!! " - // + r + " " + resDir); - //} + //if (r != null) { + // Log.w(TAG, "Throwing away out-of-date resources!!!! " + // + r + " " + resDir); + //} - AssetManager assets = new AssetManager(); - if (assets.addAssetPath(resDir) == 0) { - return null; - } + AssetManager assets = new AssetManager(); + if (assets.addAssetPath(resDir) == 0) { + return null; + } - //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); - DisplayMetrics metrics = getDisplayMetricsLocked(false); - r = new Resources(assets, metrics, getConfiguration(), compInfo); - if (false) { - Log.i(TAG, "Created app resources " + resDir + " " + r + ": " - + r.getConfiguration() + " appScale=" - + r.getCompatibilityInfo().applicationScale); + //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); + DisplayMetrics metrics = getDisplayMetricsLocked(false); + r = new Resources(assets, metrics, getConfiguration(), compInfo); + if (false) { + Log.i(TAG, "Created app resources " + resDir + " " + r + ": " + + r.getConfiguration() + " appScale=" + + r.getCompatibilityInfo().applicationScale); + } + + synchronized (mPackages) { + WeakReference<Resources> wr = mActiveResources.get(key); + Resources existing = wr != null ? wr.get() : null; + if (existing != null && existing.getAssets().isUpToDate()) { + // Someone else already created the resources while we were + // unlocked; go ahead and use theirs. + r.getAssets().close(); + return existing; } + // XXX need to remove entries when weak references go away mActiveResources.put(key, new WeakReference<Resources>(r)); return r; @@ -678,6 +691,20 @@ public final class ActivityThread { if (rd != null) { rd.performReceive(intent, resultCode, data, extras, ordered, sticky); + } else { + // The activity manager dispatched a broadcast to a registered + // receiver in this process, but before it could be delivered the + // receiver was unregistered. Acknowledge the broadcast on its + // behalf so that the system's broadcast sequence can continue. + if (DEBUG_BROADCAST) { + Log.i(TAG, "Broadcast to unregistered receiver"); + } + IActivityManager mgr = ActivityManagerNative.getDefault(); + try { + mgr.finishReceiver(this, resultCode, data, extras, false); + } catch (RemoteException e) { + Log.w(TAG, "Couldn't finish broadcast to unregistered receiver"); + } } } } @@ -703,8 +730,8 @@ public final class ActivityThread { BroadcastReceiver receiver = mReceiver; if (DEBUG_BROADCAST) { int seq = mCurIntent.getIntExtra("seq", -1); - Log.i(TAG, "Dispathing broadcast " + mCurIntent.getAction() + " seq=" + seq - + " to " + mReceiver); + Log.i(TAG, "Dispatching broadcast " + mCurIntent.getAction() + + " seq=" + seq + " to " + mReceiver); } if (receiver == null) { return; @@ -1356,13 +1383,14 @@ public final class ActivityThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, - int configChanges, boolean notResumed) { + int configChanges, boolean notResumed, Configuration config) { ActivityRecord r = new ActivityRecord(); r.token = token; r.pendingResults = pendingResults; r.pendingIntents = pendingNewIntents; r.startsNotResumed = notResumed; + r.createdConfig = config; synchronized (mRelaunchingActivities) { mRelaunchingActivities.add(r); @@ -2484,7 +2512,7 @@ public final class ActivityThread { Activity a = performLaunchActivity(r, customIntent); if (a != null) { - r.createdConfig = new Configuration(a.getResources().getConfiguration()); + r.createdConfig = new Configuration(mConfiguration); handleResumeActivity(r.token, false, r.isForward); if (!r.activity.mFinished && r.startsNotResumed) { @@ -3536,6 +3564,16 @@ public final class ActivityThread { } } + if (tmp.createdConfig != null) { + // If the activity manager is passing us its current config, + // assume that is really what we want regardless of what we + // may have pending. + if (mConfiguration == null + || mConfiguration.diff(tmp.createdConfig) != 0) { + changedConfig = tmp.createdConfig; + } + } + if (DEBUG_CONFIGURATION) Log.v(TAG, "Relaunching activity " + tmp.token + ": changedConfig=" + changedConfig); diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index a772a8f..7cba13f 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -140,7 +140,11 @@ public abstract class ApplicationThreadNative extends Binder List<Intent> pi = data.createTypedArrayList(Intent.CREATOR); int configChanges = data.readInt(); boolean notResumed = data.readInt() != 0; - scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed); + Configuration config = null; + if (data.readInt() != 0) { + config = Configuration.CREATOR.createFromParcel(data); + } + scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config); return true; } @@ -491,7 +495,8 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, - int configChanges, boolean notResumed) throws RemoteException { + int configChanges, boolean notResumed, Configuration config) + throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); @@ -499,6 +504,12 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeTypedList(pendingNewIntents); data.writeInt(configChanges); data.writeInt(notResumed ? 1 : 0); + if (config != null) { + data.writeInt(1); + config.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 89a52fd..ed810d3 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -56,7 +56,7 @@ public interface IApplicationThread extends IInterface { throws RemoteException; void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, - boolean notResumed) throws RemoteException; + boolean notResumed, Configuration config) throws RemoteException; void scheduleNewIntent(List<Intent> intent, IBinder token) throws RemoteException; void scheduleDestroyActivity(IBinder token, boolean finished, int configChanges) throws RemoteException; diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 7f5a1e7..2e94a2f 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -769,17 +769,15 @@ import java.util.List; * </tr> * * <tr><th>android:icon</th> - * <td>If provided, this icon will be shown in place of the label above the search box. - * This is a reference to a drawable (icon) resource. Note that the application icon - * is also used as an icon to the left of the search box and you cannot modify this - * behavior, so including the icon attribute is unecessary and this may be - * deprecated in the future.</td> + * <td><strong>This is deprecated.</strong><br/>The default + * application icon is now always used, so this attribute is + * obsolete.</td> * <td align="center">No</td> * </tr> * * <tr><th>android:hint</th> - * <td>This is the text to display in the search text field when no user text has been - * entered.</td> + * <td>This is the text to display in the search text field when no text + * has been entered by the user.</td> * <td align="center">No</td> * </tr> * @@ -790,17 +788,14 @@ import java.util.List; * <tbody> * <tr><th>showSearchLabelAsBadge</th> * <td>If set, this flag enables the display of the search target (label) - * above the search box. If this flag and showSearchIconAsBadge - * (see below) are both not set, no badge will be shown.</td> + * above the search box. As an alternative, you may + * want to instead use "hint" text in the search box. + * See the "android:hint" attribute above.</td> * </tr> * <tr><th>showSearchIconAsBadge</th> - * <td>If set, this flag enables the display of the search target (icon) - * above the search box. If this flag and showSearchLabelAsBadge - * (see above) are both not set, no badge will be shown. If both flags - * are set, showSearchIconAsBadge has precedence and the icon will be - * shown. Because the application icon is now used to the left of the - * search box by default, using this search mode is no longer necessary - * and may be deprecated in the future.</td> + * <td><strong>This is deprecated.</strong><br/>The default + * application icon is now always used, so this + * option is obsolete.</td> * </tr> * <tr><th>queryRewriteFromData</th> * <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_INTENT_DATA diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 34d3133..1034fab 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -101,10 +101,10 @@ public final class WallpaperInfo implements Parcelable { com.android.internal.R.styleable.Wallpaper_thumbnail, -1); authorRes = sa.getResourceId( - com.android.internal.R.styleable.Wallpaper_wallpaperAuthor, + com.android.internal.R.styleable.Wallpaper_author, -1); descriptionRes = sa.getResourceId( - com.android.internal.R.styleable.Wallpaper_wallpaperDescription, + com.android.internal.R.styleable.Wallpaper_description, -1); sa.recycle(); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index e98b286..e455a59 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -63,6 +63,21 @@ public class WallpaperManager { public static final String ACTION_LIVE_WALLPAPER_CHOOSER = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; + /** + * Command for {@link #sendWallpaperCommand}: reported by the wallpaper + * host when the user taps on an empty area (not performing an action + * in the host). The x and y arguments are the location of the tap in + * screen coordinates. + */ + public static final String COMMAND_TAP = "android.wallpaper.tap"; + + /** + * Command for {@link #sendWallpaperCommand}: reported by the wallpaper + * host when the user drops an object into an area of the host. The x + * and y arguments are the location of the drop. + */ + public static final String COMMAND_DROP = "android.home.drop"; + private final Context mContext; /** @@ -195,7 +210,12 @@ public class WallpaperManager { if (mDefaultWallpaper != null) { return mDefaultWallpaper; } - mWallpaper = getCurrentWallpaperLocked(context); + mWallpaper = null; + try { + mWallpaper = getCurrentWallpaperLocked(context); + } catch (OutOfMemoryError e) { + Log.w(TAG, "No memory load current wallpaper", e); + } if (mWallpaper == null && returnDefault) { mDefaultWallpaper = getDefaultWallpaperLocked(context); return mDefaultWallpaper; @@ -279,7 +299,12 @@ public class WallpaperManager { } catch (IOException e) { } - return generateBitmap(context, bm, width, height); + try { + return generateBitmap(context, bm, width, height); + } catch (OutOfMemoryError e) { + Log.w(TAG, "Can't generate default bitmap", e); + return bm; + } } } catch (RemoteException e) { } @@ -594,7 +619,7 @@ public class WallpaperManager { /** * For applications that use multiple virtual screens showing a wallpaper, * specify the step size between virtual screens. For example, if the - * launcher has 5 virtual screens, it would specify an xStep of 0.5, + * launcher has 3 virtual screens, it would specify an xStep of 0.5, * since the X offset for those screens are 0.0, 0.5 and 1.0 * @param xStep The X offset delta from one screen to the next one * @param yStep The Y offset delta from one screen to the next one diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index e8a69d8..7e5f858 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -42,7 +42,7 @@ import java.util.HashSet; * * Currently the BluetoothA2dp service runs in the system server and this * proxy object will be immediately bound to the service on construction. - * + * * Currently this class provides methods to connect to A2DP audio sinks. * * @hide @@ -74,12 +74,17 @@ public final class BluetoothA2dp { /** Playing implies connected */ public static final int STATE_PLAYING = 4; + /** Default priority for a2dp devices that we try to auto-connect + * and allow incoming connections */ + public static final int PRIORITY_AUTO_CONNECT = 1000; /** Default priority for a2dp devices that should allow incoming * connections */ - public static final int PRIORITY_AUTO = 100; + public static final int PRIORITY_ON = 100; /** 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; @@ -196,6 +201,22 @@ public final class BluetoothA2dp { } } + /** Check if any A2DP sink is in Non Disconnected state + * i.e playing, connected, connecting, disconnecting. + * @return a unmodifiable set of connected A2DP sinks, or null on error. + * @hide + */ + public Set<BluetoothDevice> getNonDisconnectedSinks() { + if (DBG) log("getNonDisconnectedSinks()"); + try { + return Collections.unmodifiableSet( + new HashSet<BluetoothDevice>(Arrays.asList(mService.getNonDisconnectedSinks()))); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return null; + } + } + /** Get the state of an A2DP sink * @param device Remote BT device. * @return State code, one of STATE_ diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index bd5b07c..8eda844 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -130,13 +130,13 @@ public final class BluetoothAdapter { /** * Activity Action: Show a system activity that requests discoverable mode. - * <p>This activity will also request the user to turn on Bluetooth if it + * This activity will also request the user to turn on Bluetooth if it * is not currently enabled. * <p>Discoverable mode is equivalent to {@link * #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. It allows remote devices to see * this Bluetooth adapter when they perform a discovery. - * <p>For privacy, Android is not by default discoverable. - * <p>The sender can optionally use extra field {@link + * <p>For privacy, Android is not discoverable by default. + * <p>The sender of this Intent can optionally use extra field {@link * #EXTRA_DISCOVERABLE_DURATION} to request the duration of * discoverability. Currently the default duration is 120 seconds, and * maximum duration is capped at 300 seconds for each request. @@ -147,7 +147,8 @@ public final class BluetoothAdapter { * {@link android.app.Activity#RESULT_CANCELED} if the user rejected * discoverability or an error has occurred. * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED} - * for global notification whenever the scan mode changes. + * for global notification whenever the scan mode changes. For example, an + * application can be notified when the device has ended discoverability. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @@ -369,9 +370,17 @@ public final class BluetoothAdapter { } /** - * Turn on the local Bluetooth adapter. + * Turn on the local Bluetooth adapter—do not use without explicit + * user action to turn on Bluetooth. * <p>This powers on the underlying Bluetooth hardware, and starts all * Bluetooth system services. + * <p class="caution"><strong>Bluetooth should never be enabled without + * direct user consent</strong>. If you want to turn on Bluetooth in order + * to create a wireless connection, you should use the {@link + * #ACTION_REQUEST_ENABLE} Intent, which will raise a dialog that requests + * user permission to turn on Bluetooth. The {@link #enable()} method is + * provided only for applications that include a user interface for changing + * system settings, such as a "power manager" app.</p> * <p>This is an asynchronous call: it will return immediately, and * clients should listen for {@link #ACTION_STATE_CHANGED} * to be notified of subsequent adapter state changes. If this call returns @@ -381,7 +390,8 @@ public final class BluetoothAdapter { * #STATE_ON}. If this call returns false then there was an * immediate problem that will prevent the adapter from being turned on - * such as Airplane mode, or the adapter is already turned on. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission * * @return true to indicate adapter startup has begun, or false on * immediate error @@ -394,9 +404,14 @@ public final class BluetoothAdapter { } /** - * Turn off the local Bluetooth adapter. + * Turn off the local Bluetooth adapter—do not use without explicit + * user action to turn off Bluetooth. * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth * system services, and powers down the underlying Bluetooth hardware. + * <p class="caution"><strong>Bluetooth should never be disbled without + * direct user consent</strong>. The {@link #disable()} method is + * provided only for applications that include a user interface for changing + * system settings, such as a "power manager" app.</p> * <p>This is an asynchronous call: it will return immediately, and * clients should listen for {@link #ACTION_STATE_CHANGED} * to be notified of subsequent adapter state changes. If this call returns @@ -406,7 +421,8 @@ public final class BluetoothAdapter { * #STATE_ON}. If this call returns false then there was an * immediate problem that will prevent the adapter from being turned off - * such as the adapter already being turned off. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission * * @return true to indicate adapter shutdown has begun, or false on * immediate error @@ -549,7 +565,10 @@ public final class BluetoothAdapter { * remote Bluetooth devices should not be attempted while discovery is in * progress, and existing connections will experience limited bandwidth * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing - * discovery. + * discovery. Discovery is not managed by the Activity, + * but is run as a system service, so an application should always call + * {@link BluetoothAdapter#cancelDiscovery()} even if it + * did not directly request a discovery, just to be sure. * <p>Device discovery will only find remote devices that are currently * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are * not discoverable by default, and need to be entered into a special mode. @@ -567,6 +586,13 @@ public final class BluetoothAdapter { /** * Cancel the current device discovery process. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * <p>Because discovery is a heavyweight precedure for the Bluetooth + * adapter, this method should always be called before attempting to connect + * to a remote device with {@link + * android.bluetooth.BluetoothSocket#connect()}. Discovery is not managed by + * the Activity, but is run as a system service, so an application should + * always call cancel discovery even if it did not directly request a + * discovery, just to be sure. * * @return true on success, false on error */ diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java index bc06713..c7fea9e 100644 --- a/core/java/android/bluetooth/BluetoothClass.java +++ b/core/java/android/bluetooth/BluetoothClass.java @@ -25,10 +25,6 @@ import android.os.Parcelable; * specify the general device type such as a phone, a computer, or * headset, and whether it's capable of services such as audio or telephony. * - * <p>The Bluetooth class is useful as a hint to roughly describe a device (for example to - * show an icon in the UI), but does not reliably describe which Bluetooth - * profiles or services are actually supported by a device. - * * <p>Every Bluetooth class is composed of zero or more service classes, and * exactly one device class. The device class is further broken down into major * and minor device class components. diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 6cb9770..cf9c58f 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -624,6 +624,14 @@ public final class BluetoothDevice implements Parcelable { return false; } + /** @hide */ + public boolean isBluetoothDock() { + try { + return sService.isBluetoothDock(mAddress); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + /** * Create an RFCOMM {@link BluetoothSocket} ready to start a secure * outgoing connection to this remote device on given channel. diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 90cff6b..b792965 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -100,10 +100,17 @@ public final class BluetoothHeadset { /** Connection canceled before completetion. */ public static final int RESULT_CANCELED = 2; - /** Default priority for headsets that should be auto-connected */ - public static final int PRIORITY_AUTO = 100; - /** Default priority for headsets that should not be auto-connected */ + /** Default priority for headsets that for which we will accept + * inconing connections and auto-connect */ + public static final int PRIORITY_AUTO_CONNECT = 1000; + /** Default priority for headsets that for which we will accept + * inconing connections but not auto-connect */ + public static final int PRIORITY_ON = 100; + /** 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/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index 1b23f6c..c9c6c0a 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -42,7 +42,11 @@ import java.io.IOException; * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. Then call * {@link #accept()} to listen for incoming connection requests. This call * will block until a connection is established, at which point, it will return - * a {@link BluetoothSocket} to manage the connection. + * a {@link BluetoothSocket} to manage the connection. Once the {@link + * BluetoothSocket} is acquired, it's a good idea to call {@link #close()} on + * the {@link BluetoothServerSocket} when it's no longer needed for accepting + * connections. Closing the {@link BluetoothServerSocket} will <em>not</em> + * close the returned {@link BluetoothSocket}. * * <p>{@link BluetoothServerSocket} is thread * safe. In particular, {@link #close} will always immediately abort ongoing @@ -105,6 +109,8 @@ public final class BluetoothServerSocket implements Closeable { * Immediately close this socket, and release all associated resources. * <p>Causes blocked calls on this socket in other threads to immediately * throw an IOException. + * <p>Closing the {@link BluetoothServerSocket} will <em>not</em> + * close any {@link BluetoothSocket} received from {@link #accept()}. */ public void close() throws IOException { synchronized (this) { diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index dbcc758..ad03399 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -180,6 +180,15 @@ public final class BluetoothSocket implements Closeable { * <p>This method will block until a connection is made or the connection * fails. If this method returns without an exception then this socket * is now connected. + * <p>Creating new connections to + * remote Bluetooth devices should not be attempted while device discovery + * is in progress. Device discovery is a heavyweight procedure on the + * Bluetooth adapter and will significantly slow a device connection. + * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing + * discovery. Discovery is not managed by the Activity, + * but is run as a system service, so an application should always call + * {@link BluetoothAdapter#cancelDiscovery()} even if it + * did not directly request a discovery, just to be sure. * <p>{@link #close} can be used to abort this call from another thread. * @throws IOException on error, for example connection failure */ diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 7e752af..0868779 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -64,6 +64,7 @@ interface IBluetooth boolean setTrust(in String address, in boolean value); boolean getTrustState(in String address); + boolean isBluetoothDock(in String address); int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b); void removeServiceRecord(int handle); diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 002cf4e..168fe3b 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -29,6 +29,7 @@ interface IBluetoothA2dp { boolean suspendSink(in BluetoothDevice device); boolean resumeSink(in BluetoothDevice device); BluetoothDevice[] getConnectedSinks(); // change to Set<> once AIDL supports + BluetoothDevice[] getNonDisconnectedSinks(); // change to Set<> once AIDL supports int getSinkState(in BluetoothDevice device); boolean setSinkPriority(in BluetoothDevice device, int priority); int getSinkPriority(in BluetoothDevice device); 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/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java index f483c7c..dd89097 100644 --- a/core/java/android/content/AbstractSyncableContentProvider.java +++ b/core/java/android/content/AbstractSyncableContentProvider.java @@ -135,8 +135,10 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro public void onCreate(SQLiteDatabase db) { bootstrapDatabase(db); mSyncState.createDatabase(db); - ContentResolver.requestSync(null /* all accounts */, + if (!isTemporary()) { + ContentResolver.requestSync(null /* all accounts */, mContentUri.getAuthority(), new Bundle()); + } } @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 8f1c671..799bc22 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1142,7 +1142,6 @@ public abstract class Context { * Use with {@link #getSystemService} to retrieve a * {@link android.accounts.AccountManager} for receiving intents at a * time of your choosing. - * TODO STOPSHIP perform a final review of the the account apis before shipping * * @see #getSystemService * @see android.accounts.AccountManager diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b785dbf..a96e896 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -539,10 +540,33 @@ import java.util.Set; * {@link #putExtra}. * * <ul> - * <li> {@link #EXTRA_TEMPLATE} + * <li> {@link #EXTRA_ALARM_COUNT} + * <li> {@link #EXTRA_BCC} + * <li> {@link #EXTRA_CC} + * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME} + * <li> {@link #EXTRA_DATA_REMOVED} + * <li> {@link #EXTRA_DOCK_STATE} + * <li> {@link #EXTRA_DOCK_STATE_CAR} + * <li> {@link #EXTRA_DOCK_STATE_DESK} + * <li> {@link #EXTRA_DOCK_STATE_UNDOCKED} + * <li> {@link #EXTRA_DONT_KILL_APP} + * <li> {@link #EXTRA_EMAIL} + * <li> {@link #EXTRA_INITIAL_INTENTS} * <li> {@link #EXTRA_INTENT} + * <li> {@link #EXTRA_KEY_EVENT} + * <li> {@link #EXTRA_PHONE_NUMBER} + * <li> {@link #EXTRA_REMOTE_INTENT_TOKEN} + * <li> {@link #EXTRA_REPLACING} + * <li> {@link #EXTRA_SHORTCUT_ICON} + * <li> {@link #EXTRA_SHORTCUT_ICON_RESOURCE} + * <li> {@link #EXTRA_SHORTCUT_INTENT} * <li> {@link #EXTRA_STREAM} + * <li> {@link #EXTRA_SHORTCUT_NAME} + * <li> {@link #EXTRA_SUBJECT} + * <li> {@link #EXTRA_TEMPLATE} * <li> {@link #EXTRA_TEXT} + * <li> {@link #EXTRA_TITLE} + * <li> {@link #EXTRA_UID} * </ul> * * <h3>Flags</h3> @@ -1275,12 +1299,15 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED"; /** - * Broadcast Action: An existing application package has been changed (e.g. a component has been - * enabled or disabled. The data contains the name of the package. + * Broadcast Action: An existing application package has been changed (e.g. + * a component has been enabled or disabled). The data contains the name of + * the package. * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. - * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME} containing the class name of the changed component. - * <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the default action of restarting the application. + * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST} containing the class name + * of the changed components. + * <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 @@ -1344,6 +1371,12 @@ public class Intent implements Parcelable { * 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. * @@ -1352,6 +1385,14 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 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. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED"; + /** * Broadcast Action: This is a <em>sticky broadcast</em> containing the * charging state, level, and other information about the battery. * See {@link android.os.BatteryManager} for documentation on the @@ -1661,6 +1702,7 @@ public class Intent implements Parcelable { * <ul> * <li><em>state</em> - 0 for unplugged, 1 for plugged. </li> * <li><em>name</em> - Headset type, human readable string </li> + * <li><em>microphone</em> - 1 if headset has a microphone, 0 otherwise </li> * </ul> * </ul> */ @@ -2087,14 +2129,20 @@ public class Intent implements Parcelable { "android.intent.extra.remote_intent_token"; /** - * Used as an int extra field in {@link android.content.Intent#ACTION_PACKAGE_CHANGED} - * intent to supply the name of the component that changed. - * + * @deprecated See {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST}; this field + * will contain only the first name in the list. */ - public static final String EXTRA_CHANGED_COMPONENT_NAME = + @Deprecated public static final String EXTRA_CHANGED_COMPONENT_NAME = "android.intent.extra.changed_component_name"; /** + * This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED} + * and contains a string array of all of the components that have changed. + */ + public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = + "android.intent.extra.changed_component_name_list"; + + /** * @hide * Magic extra system code can use when binding, to give a label for * who it is that has bound to a service. This is an integer giving @@ -2393,6 +2441,7 @@ public class Intent implements Parcelable { private int mFlags; private HashSet<String> mCategories; private Bundle mExtras; + private Rect mSourceBounds; // --------------------------------------------------------------------- @@ -2418,6 +2467,9 @@ public class Intent implements Parcelable { if (o.mExtras != null) { this.mExtras = new Bundle(o.mExtras); } + if (o.mSourceBounds != null) { + this.mSourceBounds = new Rect(o.mSourceBounds); + } } @Override @@ -2611,7 +2663,7 @@ public class Intent implements Parcelable { intent.mType = value; } - // launch flags + // launch flags else if (uri.startsWith("launchFlags=", i)) { intent.mFlags = Integer.decode(value).intValue(); } @@ -2631,6 +2683,11 @@ public class Intent implements Parcelable { scheme = value; } + // source bounds + else if (uri.startsWith("sourceBounds=", i)) { + intent.mSourceBounds = Rect.unflattenFromString(value); + } + // extra else { String key = Uri.decode(uri.substring(i + 2, eq)); @@ -3523,6 +3580,15 @@ public class Intent implements Parcelable { } /** + * Get the bounds of the sender of this intent, in screen coordinates. This can be + * used as a hint to the receiver for animations and the like. Null means that there + * is no source bounds. + */ + public Rect getSourceBounds() { + return mSourceBounds; + } + + /** * Return the Activity component that should be used to handle this intent. * The appropriate component is determined based on the information in the * intent, evaluated as follows: @@ -4624,6 +4690,19 @@ public class Intent implements Parcelable { } /** + * Set the bounds of the sender of this intent, in screen coordinates. This can be + * used as a hint to the receiver for animations and the like. Null means that there + * is no source bounds. + */ + public void setSourceBounds(Rect r) { + if (r != null) { + mSourceBounds = new Rect(r); + } else { + r = null; + } + } + + /** * Use with {@link #fillIn} to allow the current action value to be * overwritten, even if it is already set. */ @@ -4654,6 +4733,12 @@ public class Intent implements Parcelable { public static final int FILL_IN_PACKAGE = 1<<4; /** + * Use with {@link #fillIn} to allow the current package value to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_SOURCE_BOUNDS = 1<<5; + + /** * Copy the contents of <var>other</var> in to this object, but only * where fields are not defined by this object. For purposes of a field * being defined, the following pieces of data in the Intent are @@ -4667,6 +4752,7 @@ public class Intent implements Parcelable { * <li> package, as set by {@link #setPackage}. * <li> component, as set by {@link #setComponent(ComponentName)} or * related methods. + * <li> source bounds, as set by {@link #setSourceBounds} * <li> each top-level name in the associated extras. * </ul> * @@ -4728,6 +4814,11 @@ public class Intent implements Parcelable { changes |= FILL_IN_COMPONENT; } mFlags |= other.mFlags; + if (other.mSourceBounds != null + && (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) { + mSourceBounds = new Rect(other.mSourceBounds); + changes |= FILL_IN_SOURCE_BOUNDS; + } if (mExtras == null) { if (other.mExtras != null) { mExtras = new Bundle(other.mExtras); @@ -4981,6 +5072,13 @@ public class Intent implements Parcelable { first = false; b.append("cmp=").append(mComponent.flattenToShortString()); } + if (mSourceBounds != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("bnds=").append(mSourceBounds.toShortString()); + } if (extras && mExtras != null) { if (!first) { b.append(' '); @@ -5072,6 +5170,11 @@ public class Intent implements Parcelable { uri.append("component=").append(Uri.encode( mComponent.flattenToShortString(), "/")).append(';'); } + if (mSourceBounds != null) { + uri.append("sourceBounds=") + .append(Uri.encode(mSourceBounds.flattenToString())) + .append(';'); + } if (mExtras != null) { for (String key : mExtras.keySet()) { final Object value = mExtras.get(key); @@ -5115,6 +5218,13 @@ public class Intent implements Parcelable { out.writeString(mPackage); ComponentName.writeToParcel(mComponent, out); + if (mSourceBounds != null) { + out.writeInt(1); + mSourceBounds.writeToParcel(out, flags); + } else { + out.writeInt(0); + } + if (mCategories != null) { out.writeInt(mCategories.size()); for (String category : mCategories) { @@ -5150,6 +5260,10 @@ public class Intent implements Parcelable { mPackage = in.readString(); mComponent = ComponentName.readFromParcel(in); + if (in.readInt() != 0) { + mSourceBounds = Rect.CREATOR.createFromParcel(in); + } + int N = in.readInt(); if (N > 0) { mCategories = new HashSet<String>(); diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index ba18615..33d6159 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -34,6 +34,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.RegisteredServicesCache; import android.content.pm.ProviderInfo; +import android.content.pm.RegisteredServicesCacheListener; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; @@ -344,6 +345,14 @@ class SyncManager implements OnAccountsUpdateListener { mPackageManager = null; mSyncAdapters = new SyncAdaptersCache(mContext); + mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { + public void onServiceChanged(SyncAdapterType type, boolean removed) { + if (!removed) { + scheduleSync(null, type.authority, null, 0 /* no delay */, + false /* onlyThoseWithUnkownSyncableState */); + } + } + }, mSyncHandler); mSyncAlarmIntent = PendingIntent.getBroadcast( mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0); @@ -919,12 +928,16 @@ class SyncManager implements OnAccountsUpdateListener { + previousSyncOperation); } + // If this sync aborted because the internal sync loop retried too many times then + // don't reschedule. Otherwise we risk getting into a retry loop. // If the operation succeeded to some extent then retry immediately. // If this was a two-way sync then retry soft errors with an exponential backoff. // If this was an upward sync then schedule a two-way sync immediately. // Otherwise do not reschedule. - - if (syncResult.madeSomeProgress()) { + if (syncResult.tooManyRetries) { + Log.d(TAG, "not retrying sync operation because it retried too many times: " + + previousSyncOperation); + } else if (syncResult.madeSomeProgress()) { if (isLoggable) { Log.d(TAG, "retrying sync operation immediately because " + "even though it had an error it achieved some success"); diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 87da55f..b94bb51 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -309,15 +309,22 @@ public class ActivityInfo extends ComponentInfo public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); - pw.println(prefix + "permission=" + permission); + if (permission != null) { + pw.println(prefix + "permission=" + permission); + } pw.println(prefix + "taskAffinity=" + taskAffinity + " targetActivity=" + targetActivity); - pw.println(prefix + "launchMode=" + launchMode - + " flags=0x" + Integer.toHexString(flags) - + " theme=0x" + Integer.toHexString(theme)); - pw.println(prefix + "screenOrientation=" + screenOrientation - + " configChanges=0x" + Integer.toHexString(configChanges) - + " softInputMode=0x" + Integer.toHexString(softInputMode)); + if (launchMode != 0 || flags != 0 || theme != 0) { + pw.println(prefix + "launchMode=" + launchMode + + " flags=0x" + Integer.toHexString(flags) + + " theme=0x" + Integer.toHexString(theme)); + } + if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED + || configChanges != 0 || softInputMode != 0) { + pw.println(prefix + "screenOrientation=" + screenOrientation + + " configChanges=0x" + Integer.toHexString(configChanges) + + " softInputMode=0x" + Integer.toHexString(softInputMode)); + } super.dumpBack(pw, prefix); } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 7a65af8..1800c30 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -270,21 +270,31 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); - pw.println(prefix + "className=" + className); - pw.println(prefix + "permission=" + permission - + " uid=" + uid); - pw.println(prefix + "taskAffinity=" + taskAffinity); - pw.println(prefix + "theme=0x" + Integer.toHexString(theme)); + if (className != null) { + pw.println(prefix + "className=" + className); + } + if (permission != null) { + pw.println(prefix + "permission=" + permission); + } + pw.println(prefix + "uid=" + uid + " taskAffinity=" + taskAffinity); + if (theme != 0) { + pw.println(prefix + "theme=0x" + Integer.toHexString(theme)); + } pw.println(prefix + "flags=0x" + Integer.toHexString(flags) + " processName=" + processName); pw.println(prefix + "sourceDir=" + sourceDir); pw.println(prefix + "publicSourceDir=" + publicSourceDir); - pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles); pw.println(prefix + "dataDir=" + dataDir); - pw.println(prefix + "targetSdkVersion=" + targetSdkVersion); - pw.println(prefix + "enabled=" + enabled); - pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName); - pw.println(prefix + "description=0x"+Integer.toHexString(descriptionRes)); + if (sharedLibraryFiles != null) { + pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles); + } + pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion); + if (manageSpaceActivityName != null) { + pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName); + } + if (descriptionRes != 0) { + pw.println(prefix + "description=0x"+Integer.toHexString(descriptionRes)); + } super.dumpBack(pw, prefix); } diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 46e7ca4..8043dae 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -141,11 +141,15 @@ public class PackageItemInfo { } protected void dumpFront(Printer pw, String prefix) { - pw.println(prefix + "name=" + name); + if (name != null) { + pw.println(prefix + "name=" + name); + } pw.println(prefix + "packageName=" + packageName); - pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes) - + " nonLocalizedLabel=" + nonLocalizedLabel - + " icon=0x" + Integer.toHexString(icon)); + if (labelRes != 0 || nonLocalizedLabel != null || icon != 0) { + pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes) + + " nonLocalizedLabel=" + nonLocalizedLabel + + " icon=0x" + Integer.toHexString(icon)); + } } protected void dumpBack(Printer pw, String prefix) { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index cd48dcb..53a966d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -16,6 +16,8 @@ package android.content.pm; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -514,6 +516,78 @@ public abstract class PackageManager { public static final int DONT_DELETE_DATA = 0x00000001; /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a camera facing away + * from the screen. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA = "android.hardware.camera"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's camera supports auto-focus. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's camera supports flash. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a light sensor. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device includes a proximity sensor. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a telephony radio with data + * communication support. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY = "android.hardware.telephony"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a CDMA telephony stack. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a GSM telephony stack. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's touch screen supports multitouch. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports live wallpapers. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper"; + + /** * Retrieve overall information about an application package that is * installed on the system. * @@ -1594,21 +1668,19 @@ public abstract class PackageManager { IPackageStatsObserver observer); /** - * Add a new package to the list of preferred packages. This new package - * will be added to the front of the list (removed from its current location - * if already listed), meaning it will now be preferred over all other - * packages when resolving conflicts. - * - * @param packageName The package name of the new package to make preferred. + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superceeded + * (and conflicts with) the modern activity-based preferences. */ + @Deprecated public abstract void addPackageToPreferred(String packageName); /** - * Remove a package from the list of preferred packages. If it was on - * the list, it will no longer be preferred over other packages. - * - * @param packageName The package name to remove. + * @deprecated This function no longer does anything; it was an old + * approach to managing preferred activities, which has been superceeded + * (and conflicts with) the modern activity-based preferences. */ + @Deprecated public abstract void removePackageFromPreferred(String packageName); /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index b798bde..3f8c71e 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -851,10 +851,6 @@ public class PackageParser { } } - int maxVers = sa.getInt( - com.android.internal.R.styleable.AndroidManifestUsesSdk_maxSdkVersion, - SDK_VERSION); - sa.recycle(); if (minCode != null) { @@ -894,13 +890,6 @@ public class PackageParser { } else { pkg.applicationInfo.targetSdkVersion = targetVers; } - - if (maxVers < SDK_VERSION) { - outError[0] = "Requires older sdk version #" + maxVers - + " (current version is #" + SDK_VERSION + ")"; - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } } XmlUtils.skipCurrentTag(parser); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 0d43b2a..5894c4f 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -59,11 +59,12 @@ public final class AssetManager { private static final String TAG = "AssetManager"; private static final boolean localLOGV = Config.LOGV || false; - private static final Object mSync = new Object(); - private static final TypedValue mValue = new TypedValue(); - private static final long[] mOffsets = new long[2]; - private static AssetManager mSystem = null; + private static final Object sSync = new Object(); + private static AssetManager sSystem = null; + private final TypedValue mValue = new TypedValue(); + private final long[] mOffsets = new long[2]; + // For communication with native code. private int mObject; @@ -71,9 +72,7 @@ public final class AssetManager { private int mNumRefs = 1; private boolean mOpen = true; - private String mAssetDir; - private String mAppName; - + /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the @@ -82,7 +81,7 @@ public final class AssetManager { * {@hide} */ public AssetManager() { - synchronized (mSync) { + synchronized (this) { init(); if (localLOGV) Log.v(TAG, "New asset manager: " + this); ensureSystemAssets(); @@ -90,11 +89,11 @@ public final class AssetManager { } private static void ensureSystemAssets() { - synchronized (mSync) { - if (mSystem == null) { + synchronized (sSync) { + if (sSystem == null) { AssetManager system = new AssetManager(true); system.makeStringBlocks(false); - mSystem = system; + sSystem = system; } } } @@ -111,14 +110,14 @@ public final class AssetManager { */ public static AssetManager getSystem() { ensureSystemAssets(); - return mSystem; + return sSystem; } /** * Close this asset manager. */ public void close() { - synchronized(mSync) { + synchronized(this) { //System.out.println("Release: num=" + mNumRefs // + ", released=" + mReleased); if (mOpen) { @@ -133,7 +132,7 @@ public final class AssetManager { * identifier for the current configuration / skin. */ /*package*/ final CharSequence getResourceText(int ident) { - synchronized (mSync) { + synchronized (this) { TypedValue tmpValue = mValue; int block = loadResourceValue(ident, tmpValue, true); if (block >= 0) { @@ -151,7 +150,7 @@ public final class AssetManager { * identifier for the current configuration / skin. */ /*package*/ final CharSequence getResourceBagText(int ident, int bagEntryId) { - synchronized (mSync) { + synchronized (this) { TypedValue tmpValue = mValue; int block = loadResourceBagValue(ident, bagEntryId, tmpValue, true); if (block >= 0) { @@ -229,7 +228,7 @@ public final class AssetManager { /*package*/ final void ensureStringBlocks() { if (mStringBlocks == null) { - synchronized (mSync) { + synchronized (this) { if (mStringBlocks == null) { makeStringBlocks(true); } @@ -238,14 +237,14 @@ public final class AssetManager { } private final void makeStringBlocks(boolean copyFromSystem) { - final int sysNum = copyFromSystem ? mSystem.mStringBlocks.length : 0; + final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0; final int num = getStringBlockCount(); mStringBlocks = new StringBlock[num]; if (localLOGV) Log.v(TAG, "Making string blocks for " + this + ": " + num); for (int i=0; i<num; i++) { if (i < sysNum) { - mStringBlocks[i] = mSystem.mStringBlocks[i]; + mStringBlocks[i] = sSystem.mStringBlocks[i]; } else { mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); } @@ -293,7 +292,7 @@ public final class AssetManager { */ public final InputStream open(String fileName, int accessMode) throws IOException { - synchronized (mSync) { + synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); } @@ -308,7 +307,7 @@ public final class AssetManager { public final AssetFileDescriptor openFd(String fileName) throws IOException { - synchronized (mSync) { + synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); } @@ -384,7 +383,7 @@ public final class AssetManager { */ public final InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException { - synchronized (mSync) { + synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); } @@ -404,7 +403,7 @@ public final class AssetManager { public final AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException { - synchronized (mSync) { + synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); } @@ -463,7 +462,7 @@ public final class AssetManager { */ /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName) throws IOException { - synchronized (mSync) { + synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); } @@ -477,13 +476,13 @@ public final class AssetManager { } /*package*/ void xmlBlockGone() { - synchronized (mSync) { + synchronized (this) { decRefsLocked(); } } /*package*/ final int createTheme() { - synchronized (mSync) { + synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); } @@ -493,7 +492,7 @@ public final class AssetManager { } /*package*/ final void releaseTheme(int theme) { - synchronized (mSync) { + synchronized (this) { deleteTheme(theme); decRefsLocked(); } @@ -523,7 +522,7 @@ public final class AssetManager { return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len; } public final void close() throws IOException { - synchronized (AssetManager.mSync) { + synchronized (AssetManager.this) { if (mAsset != 0) { destroyAsset(mAsset); mAsset = 0; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index f621483..9ebf5d9 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -286,6 +286,7 @@ public class SQLiteDatabase extends SQLiteClosable { close(); } finally { Log.e(TAG, "Removing corrupt database: " + mPath); + EventLog.writeEvent(EVENT_DB_CORRUPT, mPath); // Delete the corrupt file. Don't re-create it now -- that would just confuse people // -- but the next time someone tries to open it, they can set it up from scratch. new File(mPath).delete(); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 4b733ef..d90536c 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -477,6 +477,10 @@ public class Camera { * application does not need a particular callback, a null can be passed * instead of a callback method. * + * This method will stop the preview. Applications should not call {@link + * #stopPreview()} before this. After jpeg callback is received, + * applications can call {@link #startPreview()} to restart the preview. + * * @param shutter callback after the image is captured, may be null * @param raw callback with raw image data, may be null * @param jpeg callback with jpeg image data, may be null @@ -500,6 +504,10 @@ public class Camera { * application does not need a particular callback, a null can be passed * instead of a callback method. * + * This method will stop the preview. Applications should not call {@link + * #stopPreview()} before this. After jpeg callback is received, + * applications can call {@link #startPreview()} to restart the preview. + * * @param shutter callback after the image is captured, may be null * @param raw callback with raw image data, may be null * @param postview callback with postview image data, may be null @@ -922,8 +930,8 @@ public class Camera { /** * Gets the supported preview sizes. * - * @return a List of Size object. null if preview size setting is not - * supported. + * @return a List of Size object. This method will always return a list + * with at least one element. */ public List<Size> getSupportedPreviewSizes() { String str = get(KEY_PREVIEW_SIZE + SUPPORTED_VALUES_SUFFIX); @@ -1057,8 +1065,8 @@ public class Camera { /** * Gets the supported preview formats. * - * @return a List of Integer objects. null if preview format setting is - * not supported. + * @return a List of Integer objects. This method will always return a + * list with at least one element. */ public List<Integer> getSupportedPreviewFormats() { String str = get(KEY_PREVIEW_FORMAT + SUPPORTED_VALUES_SUFFIX); @@ -1096,8 +1104,8 @@ public class Camera { /** * Gets the supported picture sizes. * - * @return a List of Size objects. null if picture size setting is not - * supported. + * @return a List of Size objects. This method will always return a list + * with at least one element. */ public List<Size> getSupportedPictureSizes() { String str = get(KEY_PICTURE_SIZE + SUPPORTED_VALUES_SUFFIX); @@ -1135,12 +1143,18 @@ public class Camera { /** * Gets the supported picture formats. * - * @return a List of Integer objects (values are PixelFormat.XXX). null - * if picture setting is not supported. + * @return a List of Integer objects (values are PixelFormat.XXX). This + * method will always return a list with at least one element. */ public List<Integer> getSupportedPictureFormats() { - String str = get(KEY_PICTURE_SIZE + SUPPORTED_VALUES_SUFFIX); - return splitInt(str); + String str = get(KEY_PICTURE_FORMAT + SUPPORTED_VALUES_SUFFIX); + ArrayList<Integer> formats = new ArrayList<Integer>(); + for (String s : split(str)) { + int f = pixelFormatForCameraFormat(s); + if (f == PixelFormat.UNKNOWN) continue; + formats.add(f); + } + return formats; } private String cameraFormatForPixelFormat(int pixel_format) { @@ -1435,8 +1449,8 @@ public class Camera { /** * Gets the supported focus modes. * - * @return a List of FOCUS_MODE_XXX string constants. null if focus mode - * setting is not supported. + * @return a List of FOCUS_MODE_XXX string constants. This method will + * always return a list with at least one element. */ public List<String> getSupportedFocusModes() { String str = get(KEY_FOCUS_MODE + SUPPORTED_VALUES_SUFFIX); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index d4aaba3..e9353d8 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -158,9 +158,14 @@ public class Build { public static final int ECLAIR = 5; /** - * Current work on Eclair MR1. + * December 2009: Android 2.0.1 */ - public static final int ECLAIR_MR1 = 6; + public static final int ECLAIR_0_1 = 6; + + /** + * January 2010: Android 2.1 + */ + public static final int ECLAIR_MR1 = 7; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl index 96d44b6..4491a8a 100644 --- a/core/java/android/os/IMountService.aidl +++ b/core/java/android/os/IMountService.aidl @@ -75,4 +75,9 @@ interface IMountService * when a UMS host is detected. */ void setAutoStartUms(boolean value); + + /** + * Shuts down the MountService and gracefully unmounts all external media. + */ + void shutdown(); } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index bcf769d..b9dc860 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -22,7 +22,7 @@ interface IPowerManager { void acquireWakeLock(int flags, IBinder lock, String tag); void goToSleep(long time); - void releaseWakeLock(IBinder lock); + void releaseWakeLock(IBinder lock, int flags); void userActivity(long when, boolean noChangeLights); void userActivityWithForce(long when, boolean noChangeLights, boolean force); void setPokeLock(int pokey, IBinder lock, String tag); diff --git a/core/java/android/os/LocalPowerManager.java b/core/java/android/os/LocalPowerManager.java index 2c6b29a..3fe21d9 100644 --- a/core/java/android/os/LocalPowerManager.java +++ b/core/java/android/os/LocalPowerManager.java @@ -45,4 +45,6 @@ public interface LocalPowerManager { // the same as the method on PowerManager public void userActivity(long time, boolean noChangeLights, int eventType); + + boolean isScreenOn(); } diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java index 3679e47..bc76180 100644 --- a/core/java/android/os/Power.java +++ b/core/java/android/os/Power.java @@ -17,6 +17,8 @@ package android.os; import java.io.IOException; +import android.os.ServiceManager; +import android.os.IMountService; /** * Class that provides access to some of the power management functions. @@ -97,5 +99,19 @@ public class Power * @throws IOException if reboot fails for some reason (eg, lack of * permission) */ - public static native void reboot(String reason) throws IOException; + public static void reboot(String reason) throws IOException + { + IMountService mSvc = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); + + if (mSvc != null) { + try { + mSvc.shutdown(); + } catch (Exception e) { + } + } + rebootNative(reason); + } + + private static native void rebootNative(String reason) throws IOException ; } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 2efc230..4b3b6f6 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -159,6 +159,15 @@ public class PowerManager public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = WAKE_BIT_PROXIMITY_SCREEN_OFF; /** + * Flag for {@link WakeLock#release release(int)} to defer releasing a + * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wakelock until the proximity sensor returns + * a negative value. + * + * {@hide} + */ + public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1; + + /** * Normally wake locks don't actually wake the device, they just cause * it to remain on once it's already on. Think of the video player * app as the normal behavior. Notifications that pop up and want @@ -267,10 +276,26 @@ public class PowerManager */ public void release() { + release(0); + } + + /** + * Release your claim to the CPU or screen being on. + * @param flags Combination of flag values to modify the release behavior. + * Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is supported. + * + * <p> + * It may turn off shortly after you release it, or it may not if there + * are other wake locks held. + * + * {@hide} + */ + public void release(int flags) + { synchronized (mToken) { if (!mRefCounted || --mCount == 0) { try { - mService.releaseWakeLock(mToken); + mService.releaseWakeLock(mToken, flags); } catch (RemoteException e) { } mHeld = false; @@ -302,7 +327,7 @@ public class PowerManager synchronized (mToken) { if (mHeld) { try { - mService.releaseWakeLock(mToken); + mService.releaseWakeLock(mToken, 0); } catch (RemoteException e) { } RuntimeInit.crash(TAG, new Exception( diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java index f9dce25..7807595 100644 --- a/core/java/android/pim/vcard/VCardComposer.java +++ b/core/java/android/pim/vcard/VCardComposer.java @@ -414,8 +414,10 @@ public class VCardComposer { appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false); appendVCardLine(builder, VCARD_PROPERTY_NAME, phoneName, needCharset, false); - String label = Integer.toString(phonetype); - appendVCardTelephoneLine(builder, phonetype, label, phoneNumber); + if (!TextUtils.isEmpty(phoneNumber)) { + String label = Integer.toString(phonetype); + appendVCardTelephoneLine(builder, phonetype, label, phoneNumber); + } appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD); diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java index 84753ee..4134dc2 100644 --- a/core/java/android/provider/Checkin.java +++ b/core/java/android/provider/Checkin.java @@ -74,6 +74,7 @@ public final class Checkin { CARRIER_BUG_REPORT, CHECKIN_FAILURE, CHECKIN_SUCCESS, + CPUFREQ_STATS, FOTA_BEGIN, FOTA_FAILURE, FOTA_INSTALL, diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 7904401..a56bb45 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -29,6 +29,7 @@ import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; import android.os.RemoteException; +import android.provider.ContactsContract.CommonDataKinds.Email; import android.text.TextUtils; import android.util.Pair; import android.view.View; @@ -37,8 +38,59 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; /** - * The contract between the contacts provider and applications. Contains definitions - * for the supported URIs and columns. These APIs supersede {@link Contacts}. + * <p> + * The contract between the contacts provider and applications. Contains + * definitions for the supported URIs and columns. These APIs supersede + * {@link Contacts}. + * </p> + * <h3>Overview</h3> + * <p> + * ContactsContract defines an extensible database of contact-related + * information. Contact information is stored in a three-tier data model: + * </p> + * <ul> + * <li> + * The {@link Data} table contains all kinds of personal data: phone numbers, + * email addresses etc. The list of data kinds that can be stored in this table + * is open-ended. There is a predefined set of common kinds, but any application + * can add its own data kinds. + * </li> + * <li> + * A row in the {@link RawContacts} table represents a set of Data describing a + * person and associated with a single account. + * </li> + * <li> + * A row in the {@link Contacts} table represents an aggregate of one or more + * RawContacts presumably describing the same person. + * </li> + * </ul> + * <p> + * Other tables include: + * </p> + * <ul> + * <li> + * {@link Groups}, which contains information about raw contact groups - the + * current API does not support the notion of groups spanning multiple accounts. + * </li> + * <li> + * {@link StatusUpdates}, which contains social status updates including IM + * availability. + * </li> + * <li> + * {@link AggregationExceptions}, which is used for manual aggregation and + * disaggregation of raw contacts + * </li> + * <li> + * {@link Settings}, which contains visibility and sync settings for accounts + * and groups. + * </li> + * <li> + * {@link SyncState}, which contains free-form data maintained on behalf of sync + * adapters + * </li> + * <li> + * {@link PhoneLookup}, which is used for quick caller-ID lookup</li> + * </ul> */ @SuppressWarnings("unused") public final class ContactsContract { @@ -128,6 +180,9 @@ public final class ContactsContract { * Generic columns for use by sync adapters. The specific functions of * these columns are private to the sync adapter. Other clients of the API * should not attempt to either read or write this column. + * + * @see RawContacts + * @see Groups */ protected interface BaseSyncColumns { @@ -144,6 +199,9 @@ public final class ContactsContract { /** * Columns that appear when each row of a table belongs to a specific * account, including sync information that an account may need. + * + * @see RawContacts + * @see Groups */ protected interface SyncColumns extends BaseSyncColumns { /** @@ -181,6 +239,13 @@ public final class ContactsContract { public static final String DIRTY = "dirty"; } + /** + * @see Contacts + * @see RawContacts + * @see ContactsContract.Data + * @see PhoneLookup + * @see ContactsContract.Contacts.AggregationSuggestions + */ protected interface ContactOptionsColumns { /** * The number of times a contact has been contacted @@ -214,6 +279,12 @@ public final class ContactsContract { public static final String SEND_TO_VOICEMAIL = "send_to_voicemail"; } + /** + * @see Contacts + * @see ContactsContract.Data + * @see PhoneLookup + * @see ContactsContract.Contacts.AggregationSuggestions + */ protected interface ContactsColumns { /** * The display name for the contact. @@ -247,6 +318,9 @@ public final class ContactsContract { public static final String LOOKUP_KEY = "lookup"; } + /** + * @see Contacts + */ protected interface ContactStatusColumns { /** * Contact presence status. See {@link StatusUpdates} for individual status @@ -270,7 +344,7 @@ public final class ContactsContract { /** * The package containing resources for this status: label and icon. - * <p>Type: NUMBER</p> + * <p>Type: TEXT</p> */ public static final String CONTACT_STATUS_RES_PACKAGE = "contact_status_res_package"; @@ -291,8 +365,194 @@ public final class ContactsContract { } /** - * Constants for the contacts table, which contains a record per group + * Constants for the contacts table, which contains a record per aggregate * of raw contacts representing the same person. + * <h3>Operations</h3> + * <dl> + * <dt><b>Insert</b></dt> + * <dd>A Contact cannot be created explicitly. When a raw contact is + * inserted, the provider will first try to find a Contact representing the + * same person. If one is found, the raw contact's + * {@link RawContacts#CONTACT_ID} column gets the _ID of the aggregate + * Contact. If no match is found, the provider automatically inserts a new + * Contact and puts its _ID into the {@link RawContacts#CONTACT_ID} column + * of the newly inserted raw contact.</dd> + * <dt><b>Update</b></dt> + * <dd>Only certain columns of Contact are modifiable: + * {@link #TIMES_CONTACTED}, {@link #LAST_TIME_CONTACTED}, {@link #STARRED}, + * {@link #CUSTOM_RINGTONE}, {@link #SEND_TO_VOICEMAIL}. Changing any of + * these columns on the Contact also changes them on all constituent raw + * contacts.</dd> + * <dt><b>Delete</b></dt> + * <dd>Be careful with deleting Contacts! Deleting an aggregate contact + * deletes all constituent raw contacts. The corresponding sync adapters + * will notice the deletions of their respective raw contacts and remove + * them from their back end storage.</dd> + * <dt><b>Query</b></dt> + * <dd> + * <ul> + * <li>If you need to read an individual contact, consider using + * {@link #CONTENT_LOOKUP_URI} instead of {@link #CONTENT_URI}.</li> + * <li>If you need to look up a contact by the phone number, use + * {@link PhoneLookup#CONTENT_FILTER_URI PhoneLookup.CONTENT_FILTER_URI}, + * which is optimized for this purpose.</li> + * <li>If you need to look up a contact by partial name, e.g. to produce + * filter-as-you-type suggestions, use the {@link #CONTENT_FILTER_URI} URI. + * <li>If you need to look up a contact by some data element like email + * address, nickname, etc, use a query against the {@link ContactsContract.Data} table. + * The result will contain contact ID, name etc. + * </ul> + * </dd> + * </dl> + * <h2>Columns</h2> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Contacts</th> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #_ID}</td> + * <td>read-only</td> + * <td>Row ID. Consider using {@link #LOOKUP_KEY} instead.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LOOKUP_KEY}</td> + * <td>read-only</td> + * <td>An opaque value that contains hints on how to find the contact if its + * row id changed as a result of a sync or aggregation.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #DISPLAY_NAME}</td> + * <td>read-only</td> + * <td>The display name for the contact. During aggregation display name is + * computed from display names of constituent raw contacts using a + * heuristic: a longer name or a name with more diacritic marks or more + * upper case characters is chosen.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #PHOTO_ID}</td> + * <td>read-only</td> + * <td>Reference to the row in the {@link ContactsContract.Data} table holding the photo. + * That row has the mime type + * {@link CommonDataKinds.Photo#CONTENT_ITEM_TYPE}. The value of this field + * is computed automatically based on the + * {@link CommonDataKinds.Photo#IS_SUPER_PRIMARY} field of the data rows of + * that mime type.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #IN_VISIBLE_GROUP}</td> + * <td>read-only</td> + * <td>An indicator of whether this contact is supposed to be visible in the + * UI. "1" if the contact has at least one raw contact that belongs to a + * visible group; "0" otherwise.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #HAS_PHONE_NUMBER}</td> + * <td>read-only</td> + * <td>An indicator of whether this contact has at least one phone number. + * "1" if there is at least one phone number, "0" otherwise.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TIMES_CONTACTED}</td> + * <td>read/write</td> + * <td>The number of times the contact has been contacted. See + * {@link #markAsContacted}. When raw contacts are aggregated, this field is + * computed automatically as the maximum number of times contacted among all + * constituent raw contacts. Setting this field automatically changes the + * corresponding field on all constituent raw contacts.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #LAST_TIME_CONTACTED}</td> + * <td>read/write</td> + * <td>The timestamp of the last time the contact was contacted. See + * {@link #markAsContacted}. Setting this field also automatically + * increments {@link #TIMES_CONTACTED}. When raw contacts are aggregated, + * this field is computed automatically as the latest time contacted of all + * constituent raw contacts. Setting this field automatically changes the + * corresponding field on all constituent raw contacts.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #STARRED}</td> + * <td>read/write</td> + * <td>An indicator for favorite contacts: '1' if favorite, '0' otherwise. + * When raw contacts are aggregated, this field is automatically computed: + * if any constituent raw contacts are starred, then this field is set to + * '1'. Setting this field automatically changes the corresponding field on + * all constituent raw contacts.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CUSTOM_RINGTONE}</td> + * <td>read/write</td> + * <td>A custom ringtone associated with a contact. Typically this is the + * URI returned by an activity launched with the + * {@link android.media.RingtoneManager#ACTION_RINGTONE_PICKER} intent.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SEND_TO_VOICEMAIL}</td> + * <td>read/write</td> + * <td>An indicator of whether calls from this contact should be forwarded + * directly to voice mail ('1') or not ('0'). When raw contacts are + * aggregated, this field is automatically computed: if <i>all</i> + * constituent raw contacts have SEND_TO_VOICEMAIL=1, then this field is set + * to '1'. Setting this field automatically changes the corresponding field + * on all constituent raw contacts.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #CONTACT_PRESENCE}</td> + * <td>read-only</td> + * <td>Contact IM presence status. See {@link StatusUpdates} for individual + * status definitions. Automatically computed as the highest presence of all + * constituent raw contacts. The provider may choose not to store this value + * in persistent storage. The expectation is that presence status will be + * updated on a regular basic.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CONTACT_STATUS}</td> + * <td>read-only</td> + * <td>Contact's latest status update. Automatically computed as the latest + * of all constituent raw contacts' status updates.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_STATUS_TIMESTAMP}</td> + * <td>read-only</td> + * <td>The absolute time in milliseconds when the latest status was + * inserted/updated.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CONTACT_STATUS_RES_PACKAGE}</td> + * <td>read-only</td> + * <td> The package containing resources for this status: label and icon.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_STATUS_LABEL}</td> + * <td>read-only</td> + * <td>The resource ID of the label describing the source of contact status, + * e.g. "Google Talk". This resource is scoped by the + * {@link #CONTACT_STATUS_RES_PACKAGE}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_STATUS_ICON}</td> + * <td>read-only</td> + * <td>The resource ID of the icon for the source of contact status. This + * resource is scoped by the {@link #CONTACT_STATUS_RES_PACKAGE}.</td> + * </tr> + * </table> */ public static class Contacts implements BaseColumns, ContactsColumns, ContactOptionsColumns, ContactStatusColumns { @@ -365,7 +625,7 @@ public final class ContactsContract { /** * Build a {@link #CONTENT_LOOKUP_URI} lookup {@link Uri} using the - * given {@link android.provider.ContactsContract.Contacts#_ID} and {@link #LOOKUP_KEY}. + * given {@link ContactsContract.Contacts#_ID} and {@link #LOOKUP_KEY}. */ public static Uri getLookupUri(long contactId, String lookupKey) { return ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, @@ -423,7 +683,7 @@ public final class ContactsContract { /** * The content:// style URI for this table joined with useful data from - * {@link Data}, filtered to include only starred contacts + * {@link ContactsContract.Data}, filtered to include only starred contacts * and the most frequently contacted contacts. */ public static final Uri CONTENT_STREQUENT_URI = Uri.withAppendedPath( @@ -461,7 +721,7 @@ public final class ContactsContract { /** * A sub-directory of a single contact that contains all of the constituent raw contact - * {@link Data} rows. + * {@link ContactsContract.Data} rows. */ public static final class Data implements BaseColumns, DataColumns { /** @@ -476,10 +736,33 @@ public final class ContactsContract { } /** - * A sub-directory of a single contact aggregate that contains all aggregation suggestions - * (other contacts). The aggregation suggestions are computed based on approximate - * data matches with this contact. + * <p> + * A <i>read-only</i> sub-directory of a single contact aggregate that + * contains all aggregation suggestions (other contacts). The + * aggregation suggestions are computed based on approximate data + * matches with this contact. + * </p> + * <p> + * <i>Note: this query may be expensive! If you need to use it in bulk, + * make sure the user experience is acceptable when the query runs for a + * long time.</i> + * <p> + * Usage example: + * + * <pre> + * Uri uri = Contacts.CONTENT_URI.buildUpon() + * .appendEncodedPath(String.valueOf(contactId)) + * .appendPath(Contacts.AggregationSuggestions.CONTENT_DIRECTORY) + * .appendQueryParameter("limit", "3") + * .build() + * Cursor cursor = getContentResolver().query(suggestionsUri, + * new String[] {Contacts.DISPLAY_NAME, Contacts._ID, Contacts.LOOKUP_KEY}, + * null, null, null); + * </pre> + * + * </p> */ + // TODO: add ContactOptionsColumns, ContactStatusColumns public static final class AggregationSuggestions implements BaseColumns, ContactsColumns { /** * No public constructor since this is a utility class @@ -495,8 +778,40 @@ public final class ContactsContract { } /** - * A sub-directory of a single contact that contains the contact's primary photo. + * A <i>read-only</i> sub-directory of a single contact that contains + * the contact's primary photo. + * <p> + * Usage example: + * + * <pre> + * public InputStream openPhoto(long contactId) { + * Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + * Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY); + * Cursor cursor = getContentResolver().query(photoUri, + * new String[] {Contacts.Photo.PHOTO}, null, null, null); + * if (cursor == null) { + * return null; + * } + * try { + * if (cursor.moveToFirst()) { + * byte[] data = cursor.getBlob(0); + * if (data != null) { + * return new ByteArrayInputStream(data); + * } + * } + * } finally { + * cursor.close(); + * } + * return null; + * } + * </pre> + * + * </p> + * <p>You should also consider using the convenience method + * {@link ContactsContract.Contacts#openContactPhotoInputStream(ContentResolver, Uri)} + * </p> */ + // TODO: change DataColumns to DataColumnsWithJoins public static final class Photo implements BaseColumns, DataColumns { /** * no public constructor since this is a utility class @@ -507,6 +822,15 @@ public final class ContactsContract { * The directory twig for this sub-table */ public static final String CONTENT_DIRECTORY = "photo"; + + /** + * Thumbnail photo of the raw contact. This is the raw bytes of an image + * that could be inflated using {@link android.graphics.BitmapFactory}. + * <p> + * Type: BLOB + * @hide TODO: Unhide in a separate CL + */ + public static final String PHOTO = DATA15; } /** @@ -542,7 +866,7 @@ public final class ContactsContract { protected interface RawContactsColumns { /** - * A reference to the {@link android.provider.ContactsContract.Contacts#_ID} that this + * A reference to the {@link ContactsContract.Contacts#_ID} that this * data belongs to. * <P>Type: INTEGER</P> */ @@ -580,6 +904,315 @@ public final class ContactsContract { * Constants for the raw contacts table, which contains the base contact * information per sync source. Sync adapters and contact management apps * are the primary consumers of this API. + * <h3>Operations</h3> + * <dl> + * <dt><b>Insert</b></dt> + * <dd>There are two mechanisms that can be used to insert a raw contact: incremental and + * batch. The incremental method is more traditional but less efficient. It should be used + * only if the constituent data rows are unavailable at the time the raw contact is created: + * <pre> + * ContentValues values = new ContentValues(); + * values.put(RawContacts.ACCOUNT_TYPE, accountType); + * values.put(RawContacts.ACCOUNT_NAME, accountName); + * Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values); + * long rawContactId = ContentUris.parseId(rawContactUri); + * </pre> + * <p> + * Once data rows are available, insert those. For example, here's how you would insert + * a name: + * + * <pre> + * values.clear(); + * values.put(Data.RAW_CONTACT_ID, rawContactId); + * values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + * values.put(StructuredName.DISPLAY_NAME, "Mike Sullivan"); + * getContentResolver().insert(Data.CONTENT_URI, values); + * </pre> + * </p> + * <p> + * The batch method is by far preferred. It inserts the raw contact and its + * constituent data rows in a single database transaction + * and causes at most one aggregation pass. + * <pre> + * ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); + * int rawContactInsertIndex = ops.size(); + * ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + * .withValue(RawContacts.ACCOUNT_TYPE, accountType) + * .withValue(RawContacts.ACCOUNT_NAME, accountName) + * .build()); + * + * ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) + * .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) + * .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + * .withValue(StructuredName.DISPLAY_NAME, "Mike Sullivan") + * .build()); + * + * getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + * </pre> + * </p> + * <p> + * Please note the use of back reference in the construction of the + * {@link ContentProviderOperation}. It allows an operation to use the result of + * a previous operation by referring to it by its index in the batch. + * </p> + * <dt><b>Update</b></dt> + * <dd><p>Just as with insert, the update can be done incrementally or as a batch, the + * batch mode being the preferred method.</p></dd> + * <dt><b>Delete</b></dt> + * <dd><p>When a raw contact is deleted, all of its Data rows as well as StatusUpdates, + * AggregationExceptions, PhoneLookup rows are deleted automatically. When all raw + * contacts in a Contact are deleted, the Contact itself is also deleted automatically. + * </p> + * <p> + * The invocation of {@code resolver.delete(...)}, does not physically delete + * a raw contacts row. It sets the {@link #DELETED} flag on the raw contact and + * removes the raw contact from its aggregate contact. + * The sync adapter then deletes the raw contact from the server and + * finalizes phone-side deletion by calling {@code resolver.delete(...)} + * again and passing the {@link #CALLER_IS_SYNCADAPTER} query parameter.<p> + * <p>Some sync adapters are read-only, meaning that they only sync server-side + * changes to the phone, but not the reverse. If one of those raw contacts + * is marked for deletion, it will remain on the phone. However it will be + * effectively invisible, because it will not be part of any aggregate contact. + * </dd> + * <dt><b>Query</b></dt> + * <dd> + * <p> + * Finding all raw contacts in a Contact is easy: + * <pre> + * Cursor c = getContentResolver().query(RawContacts.CONTENT_URI, + * new String[]{RawContacts._ID}, + * RawContacts.CONTACT_ID + "=?", + * new String[]{String.valueOf(contactId)}, null); + * </pre> + * </p> + * <p> + * There are two ways to find raw contacts within a specific account, + * you can either put the account name and type in the selection or pass them as query + * parameters. The latter approach is preferable, especially when you can reuse the + * URI: + * <pre> + * Uri rawContactUri = RawContacts.URI.buildUpon() + * .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName) + * .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType) + * .build(); + * Cursor c1 = getContentResolver().query(rawContactUri, + * RawContacts.STARRED + "<>0", null, null, null); + * ... + * Cursor c2 = getContentResolver().query(rawContactUri, + * RawContacts.DELETED + "<>0", null, null, null); + * </pre> + * </p> + * <p>The best way to read a raw contact along with all the data associated with it is + * by using the {@link Entity} directory. If the raw contact has data rows, + * the Entity cursor will contain a row for each data row. If the raw contact has no + * data rows, the cursor will still contain one row with the raw contact-level information. + * <pre> + * Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); + * Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY); + * Cursor c = getContentResolver().query(entityUri, + * new String[]{RawContacts.SOURCE_ID, Entity.DATA_ID, Entity.MIMETYPE, Entity.DATA1}, + * null, null, null); + * try { + * while (c.moveToNext()) { + * String sourceId = c.getString(0); + * if (!c.isNull(1)) { + * String mimeType = c.getString(2); + * String data = c.getString(3); + * ... + * } + * } + * } finally { + * c.close(); + * } + * </pre> + * </p> + * </dd> + * </dl> + * <h3>Aggregation</h3> + * <p> + * As soon as a raw contact is inserted or whenever its constituent data + * changes, the provider will check if the raw contact matches other + * existing raw contacts and if so will aggregate it with those. From the + * data standpoint, aggregation is reflected in the change of the + * {@link #CONTACT_ID} field, which is the reference to the aggregate contact. + * </p> + * <p> + * See also {@link AggregationExceptions} for a mechanism to control + * aggregation programmatically. + * </p> + * <h2>Columns</h2> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>RawContacts</th> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #_ID}</td> + * <td>read-only</td> + * <td>Row ID. Sync adapter should try to preserve row IDs during updates. In other words, + * it would be a really bad idea to delete and reinsert a raw contact. A sync adapter should + * always do an update instead.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_ID}</td> + * <td>read-only</td> + * <td>A reference to the {@link ContactsContract.Contacts#_ID} that this raw contact belongs + * to. Raw contacts are linked to contacts by the aggregation process, which can be controlled + * by the {@link #AGGREGATION_MODE} field and {@link AggregationExceptions}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #AGGREGATION_MODE}</td> + * <td>read/write</td> + * <td>A mechanism that allows programmatic control of the aggregation process. The allowed + * values are {@link #AGGREGATION_MODE_DEFAULT}, {@link #AGGREGATION_MODE_DISABLED} + * and {@link #AGGREGATION_MODE_SUSPENDED}. See also {@link AggregationExceptions}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #DELETED}</td> + * <td>read/write</td> + * <td>The "deleted" flag: "0" by default, "1" if the row has been marked + * for deletion. When {@link android.content.ContentResolver#delete} is + * called on a raw contact, it is marked for deletion and removed from its + * aggregate contact. The sync adaptor deletes the raw contact on the server and + * then calls ContactResolver.delete once more, this time passing the + * {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter to finalize + * the data removal.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TIMES_CONTACTED}</td> + * <td>read/write</td> + * <td>The number of times the contact has been contacted. To have an effect + * on the corresponding value of the aggregate contact, this field + * should be set at the time the raw contact is inserted. + * See {@link ContactsContract.Contacts#markAsContacted}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #LAST_TIME_CONTACTED}</td> + * <td>read/write</td> + * <td>The timestamp of the last time the contact was contacted. To have an effect + * on the corresponding value of the aggregate contact, this field + * should be set at the time the raw contact is inserted. + * See {@link ContactsContract.Contacts#markAsContacted}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #STARRED}</td> + * <td>read/write</td> + * <td>An indicator for favorite contacts: '1' if favorite, '0' otherwise. + * Changing this field immediately effects the corresponding aggregate contact: + * if any raw contacts in that aggregate contact are starred, then the contact + * itself is marked as starred.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CUSTOM_RINGTONE}</td> + * <td>read/write</td> + * <td>A custom ringtone associated with a raw contact. Typically this is the + * URI returned by an activity launched with the + * {@link android.media.RingtoneManager#ACTION_RINGTONE_PICKER} intent. + * To have an effect on the corresponding value of the aggregate contact, this field + * should be set at the time the raw contact is inserted. To set a custom + * ringtone on a contact, use the field {@link ContactsContract.Contacts#CUSTOM_RINGTONE} + * instead.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SEND_TO_VOICEMAIL}</td> + * <td>read/write</td> + * <td>An indicator of whether calls from this raw contact should be forwarded + * directly to voice mail ('1') or not ('0'). To have an effect + * on the corresponding value of the aggregate contact, this field + * should be set at the time the raw contact is inserted.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #ACCOUNT_NAME}</td> + * <td>read/write-once</td> + * <td>The name of the account instance to which this row belongs, which when paired with + * {@link #ACCOUNT_TYPE} identifies a specific account. It should be set at the time + * the raw contact is inserted and never changed afterwards.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #ACCOUNT_TYPE}</td> + * <td>read/write-once</td> + * <td>The type of account to which this row belongs, which when paired with + * {@link #ACCOUNT_NAME} identifies a specific account. It should be set at the time + * the raw contact is inserted and never changed afterwards.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SOURCE_ID}</td> + * <td>read/write</td> + * <td>String that uniquely identifies this row to its source account. + * Typically it is set at the time the raw contact is inserted and never + * changed afterwards. The one notable exception is a new raw contact: it + * will have an account name and type, but no source id. This should + * indicated to the sync adapter that a new contact needs to be created + * server-side and its ID stored in the corresponding SOURCE_ID field on + * the phone. + * </td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #VERSION}</td> + * <td>read-only</td> + * <td>Version number that is updated whenever this row or its related data + * changes. This field can be used for optimistic locking of a raw contact. + * </td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #DIRTY}</td> + * <td>read/write</td> + * <td>Flag indicating that {@link #VERSION} has changed, and this row needs + * to be synchronized by its owning account. The value is set to "1" automatically + * whenever the raw contact changes, unless the URI has the + * {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter specified. + * The sync adapter should always supply this query parameter to prevent + * unnecessary synchronization: user changes some data on the server, + * the sync adapter updates the contact on the phone (without the + * CALLER_IS_SYNCADAPTER flag) flag, which sets the DIRTY flag, + * which triggers a sync to bring the changes to the server. + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SYNC1}</td> + * <td>read/write</td> + * <td>Generic column for use by sync adapters. Content provider + * stores this information on behalf of the sync adapter but does not + * interpret it in any way. + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SYNC2}</td> + * <td>read/write</td> + * <td>Generic column for use by sync adapters. + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SYNC3}</td> + * <td>read/write</td> + * <td>Generic column for use by sync adapters. + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SYNC4}</td> + * <td>read/write</td> + * <td>Generic column for use by sync adapters. + * </td> + * </tr> + * </table> */ public static final class RawContacts implements BaseColumns, RawContactsColumns, ContactOptionsColumns, SyncColumns { @@ -613,6 +1246,7 @@ public final class ContactsContract { /** * Aggregation mode: aggregate at the time the raw contact is inserted/updated. + * TODO: deprecate. Aggregation is now synchronous, this value is a no-op */ public static final int AGGREGATION_MODE_IMMEDIATE = 1; @@ -658,8 +1292,9 @@ public final class ContactsContract { } /** - * A sub-directory of a single raw contact that contains all of their {@link Data} rows. - * To access this directory append {@link Data#CONTENT_DIRECTORY} to the contact URI. + * A sub-directory of a single raw contact that contains all of their + * {@link ContactsContract.Data} rows. To access this directory + * append {@link Data#CONTENT_DIRECTORY} to the contact URI. */ public static final class Data implements BaseColumns, DataColumns { /** @@ -675,8 +1310,27 @@ public final class ContactsContract { } /** - * A sub-directory of a single raw contact that contains all of their {@link Data} rows. - * To access this directory append {@link Entity#CONTENT_DIRECTORY} to the contact URI. + * <p> + * A sub-directory of a single raw contact that contains all of their + * {@link ContactsContract.Data} rows. To access this directory append + * {@link Entity#CONTENT_DIRECTORY} to the contact URI. See + * {@link RawContactsEntity} for a stand-alone table containing the same + * data. + * </p> + * <p> + * The Entity directory is similar to the {@link RawContacts.Data} + * directory but with two important differences: + * <ul> + * <li>Entity has different ID fields: {@link #_ID} for the raw contact + * and {@link #DATA_ID} for the data rows.</li> + * <li>Entity always contains at least one row, even if there are no + * actual data rows. In this case the {@link #DATA_ID} field will be + * null.</li> + * </ul> + * Using Entity should preferred to using two separate queries: + * RawContacts followed by Data. The reason is that Entity reads all + * data for a raw contact in one transaction, so there is no possibility + * of the data changing between the two queries. */ public static final class Entity implements BaseColumns, DataColumns { /** @@ -699,6 +1353,12 @@ public final class ContactsContract { } } + /** + * Social status update columns. + * + * @see StatusUpdates + * @see ContactsContract.Data + */ protected interface StatusColumns extends Im.CommonPresenceColumns { /** * Contact's latest presence level. @@ -739,6 +1399,11 @@ public final class ContactsContract { public static final String STATUS_ICON = "status_icon"; } + /** + * Columns in the Data table. + * + * @see ContactsContract.Data + */ protected interface DataColumns { /** * The package name to use when creating {@link Resources} objects for @@ -824,7 +1489,9 @@ public final class ContactsContract { } /** - * Combines all columns returned by {@link Data} table queries. + * Combines all columns returned by {@link ContactsContract.Data} table queries. + * + * @see ContactsContract.Data */ protected interface DataColumnsWithJoins extends BaseColumns, DataColumns, StatusColumns, RawContactsColumns, ContactsColumns, ContactOptionsColumns, ContactStatusColumns { @@ -832,10 +1499,468 @@ public final class ContactsContract { } /** - * Constants for the data table, which contains data points tied to a raw contact. - * For example, a phone number or email address. Each row in this table contains a type - * definition and some generic columns. Each data type can define the meaning for each of - * the generic columns. + * <p> + * Constants for the data table, which contains data points tied to a raw + * contact. For example, a phone number or email address. + * </p> + * <h3>Data kinds</h3> + * <p> + * Data is a generic table that can hold all kinds of data. Sync adapters + * and applications can introduce their own data kinds. The kind of data + * stored in a particular row is determined by the mime type in the row. + * Fields from {@link #DATA1} through {@link #DATA15} are generic columns + * whose specific use is determined by the kind of data stored in the row. + * For example, if the data kind is + * {@link CommonDataKinds.Phone Phone.CONTENT_ITEM_TYPE}, then DATA1 stores the + * phone number, but if the data kind is + * {@link CommonDataKinds.Email Email.CONTENT_ITEM_TYPE}, then DATA1 stores the + * email address. + * </p> + * <p> + * ContactsContract defines a small number of common data kinds, e.g. + * {@link CommonDataKinds.Phone}, {@link CommonDataKinds.Email} etc. As a + * convenience, these classes define data kind specific aliases for DATA1 etc. + * For example, {@link CommonDataKinds.Phone Phone.NUMBER} is the same as + * {@link ContactsContract.Data Data.DATA1}. + * </p> + * <p> + * {@link #DATA1} is an indexed column and should be used for the data element that is + * expected to be most frequently used in query selections. For example, in the + * case of a row representing email addresses {@link #DATA1} should probably + * be used for the email address itself, while {@link #DATA2} etc can be + * used for auxiliary information like type of email address. + * <p> + * <p> + * By convention, {@link #DATA15} is used for storing BLOBs (binary data). + * </p> + * <p> + * Typically you should refrain from introducing new kinds of data for 3rd + * party account types. For example, if you add a data row for + * "favorite song" to a raw contact owned by a Google account, it will not + * get synced to the server, because the Google sync adapter does not know + * how to handle this data kind. Thus new data kinds are typically + * introduced along with new account types, i.e. new sync adapters. + * </p> + * <h3>Batch operations</h3> + * <p> + * Data rows can be inserted/updated/deleted using the traditional + * {@link ContentResolver#insert}, {@link ContentResolver#update} and + * {@link ContentResolver#delete} methods, however the newer mechanism based + * on a batch of {@link ContentProviderOperation} will prove to be a better + * choice in almost all cases. All operations in a batch are executed in a + * single transaction, which ensures that the phone-side and server-side + * state of a raw contact are always consistent. Also, the batch-based + * approach is far more efficient: not only are the database operations + * faster when executed in a single transaction, but also sending a batch of + * commands to the content provider saves a lot of time on context switching + * between your process and the process in which the content provider runs. + * </p> + * <p> + * The flip side of using batched operations is that a large batch may lock + * up the database for a long time preventing other applications from + * accessing data and potentially causing ANRs ("Application Not Responding" + * dialogs.) + * </p> + * <p> + * To avoid such lockups of the database, make sure to insert "yield points" + * in the batch. A yield point indicates to the content provider that before + * executing the next operation it can commit the changes that have already + * been made, yield to other requests, open another transaction and continue + * processing operations. A yield point will not automatically commit the + * transaction, but only if there is another request waiting on the + * database. Normally a sync adapter should insert a yield point at the + * beginning of each raw contact operation sequence in the batch. See + * {@link ContentProviderOperation.Builder#withYieldAllowed(boolean)}. + * </p> + * <h3>Operations</h3> + * <dl> + * <dt><b>Insert</b></dt> + * <dd> + * <p> + * An individual data row can be inserted using the traditional + * {@link ContentResolver#insert(Uri, ContentValues)} method. Multiple rows + * should always be inserted as a batch. + * </p> + * <p> + * An example of a traditional insert: + * <pre> + * ContentValues values = new ContentValues(); + * values.put(Data.RAW_CONTACT_ID, rawContactId); + * values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + * values.put(Phone.NUMBER, "1-800-GOOG-411"); + * values.put(Phone.TYPE, Phone.TYPE_CUSTOM); + * values.put(Phone.LABEL, "free directory assistance"); + * Uri dataUri = getContentResolver().insert(Data.CONTENT_URI, values); + * </pre> + * <p> + * The same done using ContentProviderOperations: + * <pre> + * ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); + * ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) + * .withValue(Data.RAW_CONTACT_ID, rawContactId) + * .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) + * .withValue(Phone.NUMBER, "1-800-GOOG-411") + * .withValue(Phone.TYPE, Phone.TYPE_CUSTOM) + * .withValue(Phone.LABEL, "free directory assistance") + * .build()); + * getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + * </pre> + * </p> + * <dt><b>Update</b></dt> + * <dd> + * <p> + * Just as with insert, update can be done incrementally or as a batch, + * the batch mode being the preferred method: + * <pre> + * ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); + * ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) + * .withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)}) + * .withValue(Email.DATA, "somebody@android.com") + * .build()); + * getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + * </pre> + * </p> + * </dd> + * <dt><b>Delete</b></dt> + * <dd> + * <p> + * Just as with insert and update, deletion can be done either using the + * {@link ContentResolver#delete} method or using a ContentProviderOperation: + * <pre> + * ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); + * ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) + * .withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)}) + * .build()); + * getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + * </pre> + * </p> + * </dd> + * <dt><b>Query</b></dt> + * <dd> + * <p> + * <dl> + * <dt>Finding all Data of a given type for a given contact</dt> + * <dd> + * <pre> + * Cursor c = getContentResolver().query(Data.CONTENT_URI, + * new String[] {Data._ID, Phone.NUMBER, Phone.TYPE, Phone.LABEL}, + * Data.CONTACT_ID + "=?" + " AND " + * + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'", + * new String[] {String.valueOf(contactId)}, null); + * </pre> + * </p> + * <p> + * </dd> + * <dt>Finding all Data of a given type for a given raw contact</dt> + * <dd> + * <pre> + * Cursor c = getContentResolver().query(Data.CONTENT_URI, + * new String[] {Data._ID, Phone.NUMBER, Phone.TYPE, Phone.LABEL}, + * Data.RAW_CONTACT_ID + "=?" + " AND " + * + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'", + * new String[] {String.valueOf(rawContactId)}, null); + * </pre> + * </dd> + * <dt>Finding all Data for a given raw contact</dt> + * <dd> + * Most sync adapters will want to read all data rows for a raw contact + * along with the raw contact itself. For that you should use the + * {@link RawContactsEntity}. See also {@link RawContacts}. + * </dd> + * </dl> + * </p> + * </dd> + * </dl> + * <h2>Columns</h2> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Data</th> + * </tr> + * <tr> + * <td style="width: 7em;">long</td> + * <td style="width: 20em;">{@link #_ID}</td> + * <td style="width: 5em;">read-only</td> + * <td>Row ID. Sync adapter should try to preserve row IDs during updates. In other words, + * it would be a bad idea to delete and reinsert a data rows. A sync adapter should + * always do an update instead.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #MIMETYPE}</td> + * <td>read/write-once</td> + * <td> + * <p>The MIME type of the item represented by this row. Examples of common + * MIME types are: + * <ul> + * <li>{@link CommonDataKinds.StructuredName StructuredName.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Phone Phone.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Email Email.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Photo Photo.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Organization Organization.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Im Im.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Nickname Nickname.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Note Note.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.StructuredPostal StructuredPostal.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.GroupMembership GroupMembership.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Website Website.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Event Event.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.Relation Relation.CONTENT_ITEM_TYPE}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #RAW_CONTACT_ID}</td> + * <td>read/write-once</td> + * <td>A reference to the {@link RawContacts#_ID} that this data belongs to.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_ID}</td> + * <td>read-only</td> + * <td>A reference to the {@link ContactsContract.Contacts#_ID} that this data row belongs + * to. It is obtained through a join with RawContacts.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #IS_PRIMARY}</td> + * <td>read/write</td> + * <td>Whether this is the primary entry of its kind for the raw contact it belongs to. + * "1" if true, "0" if false.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #IS_SUPER_PRIMARY}</td> + * <td>read/write</td> + * <td>Whether this is the primary entry of its kind for the aggregate + * contact it belongs to. Any data record that is "super primary" must + * also be "primary".</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #DATA_VERSION}</td> + * <td>read-only</td> + * <td>The version of this data record. Whenever the data row changes + * the version goes up. This value is monotonically increasing.</td> + * </tr> + * <tr> + * <td>Any type</td> + * <td> + * {@link #DATA1}<br> + * {@link #DATA2}<br> + * {@link #DATA3}<br> + * {@link #DATA4}<br> + * {@link #DATA5}<br> + * {@link #DATA6}<br> + * {@link #DATA7}<br> + * {@link #DATA8}<br> + * {@link #DATA9}<br> + * {@link #DATA10}<br> + * {@link #DATA11}<br> + * {@link #DATA12}<br> + * {@link #DATA13}<br> + * {@link #DATA14}<br> + * {@link #DATA15} + * </td> + * <td>read/write</td> + * <td>Generic data columns, the meaning is {@link #MIMETYPE} specific.</td> + * </tr> + * <tr> + * <td>Any type</td> + * <td> + * {@link #SYNC1}<br> + * {@link #SYNC2}<br> + * {@link #SYNC3}<br> + * {@link #SYNC4} + * </td> + * <td>read/write</td> + * <td>Generic columns for use by sync adapters. For example, a Photo row + * may store the image URL in SYNC1, a status (not loaded, loading, loaded, error) + * in SYNC2, server-side version number in SYNC3 and error code in SYNC4.</td> + * </tr> + * </table> + * + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Join with {@link StatusUpdates}</th> + * </tr> + * <tr> + * <td style="width: 7em;">int</td> + * <td style="width: 20em;">{@link #PRESENCE}</td> + * <td style="width: 5em;">read-only</td> + * <td>IM presence status linked to this data row. Compare with + * {@link #CONTACT_PRESENCE}, which contains the contact's presence across + * all IM rows. See {@link StatusUpdates} for individual status definitions. + * The provider may choose not to store this value + * in persistent storage. The expectation is that presence status will be + * updated on a regular basic. + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #STATUS}</td> + * <td>read-only</td> + * <td>Latest status update linked with this data row.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #STATUS_TIMESTAMP}</td> + * <td>read-only</td> + * <td>The absolute time in milliseconds when the latest status was + * inserted/updated for this data row.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #STATUS_RES_PACKAGE}</td> + * <td>read-only</td> + * <td>The package containing resources for this status: label and icon.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #STATUS_LABEL}</td> + * <td>read-only</td> + * <td>The resource ID of the label describing the source of status update linked + * to this data row. This resource is scoped by the {@link #STATUS_RES_PACKAGE}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #STATUS_ICON}</td> + * <td>read-only</td> + * <td>The resource ID of the icon for the source of the status update linked + * to this data row. This resource is scoped by the {@link #STATUS_RES_PACKAGE}.</td> + * </tr> + * </table> + * + * <p> + * Columns from the associated raw contact are also available through an + * implicit join. + * </p> + * + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Join with {@link RawContacts}</th> + * </tr> + * <tr> + * <td style="width: 7em;">int</td> + * <td style="width: 20em;">{@link #AGGREGATION_MODE}</td> + * <td style="width: 5em;">read-only</td> + * <td>See {@link RawContacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #DELETED}</td> + * <td>read-only</td> + * <td>See {@link RawContacts}.</td> + * </tr> + * </table> + * + * <p> + * Columns from the associated aggregated contact are also available through an + * implicit join. + * </p> + * + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Join with {@link Contacts}</th> + * </tr> + * <tr> + * <td style="width: 7em;">String</td> + * <td style="width: 20em;">{@link #LOOKUP_KEY}</td> + * <td style="width: 5em;">read-only</td> + * <td>See {@link ContactsContract.Contacts}</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #DISPLAY_NAME}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #PHOTO_ID}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #IN_VISIBLE_GROUP}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #HAS_PHONE_NUMBER}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TIMES_CONTACTED}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #LAST_TIME_CONTACTED}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #STARRED}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CUSTOM_RINGTONE}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SEND_TO_VOICEMAIL}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #CONTACT_PRESENCE}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CONTACT_STATUS}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_STATUS_TIMESTAMP}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CONTACT_STATUS_RES_PACKAGE}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_STATUS_LABEL}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_STATUS_ICON}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * </table> */ public final static class Data implements DataColumnsWithJoins { /** @@ -859,7 +1984,7 @@ public final class ContactsContract { * * This flag is useful (currently) only for vCard exporter in Contacts app, which * needs to exclude "un-exportable" data from available data to export, while - * Contacts app itself has priviledge to access all data including "un-expotable" + * Contacts app itself has priviledge to access all data including "un-exportable" * ones and providers return all of them regardless of the callers' intention. * <P>Type: INTEGER</p> * @@ -872,7 +1997,7 @@ public final class ContactsContract { /** * Build a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} * style {@link Uri} for the parent {@link android.provider.ContactsContract.Contacts} - * entry of the given {@link Data} entry. + * entry of the given {@link ContactsContract.Data} entry. */ public static Uri getContactLookupUri(ContentResolver resolver, Uri dataUri) { final Cursor cursor = resolver.query(dataUri, new String[] { @@ -894,8 +2019,141 @@ public final class ContactsContract { } /** - * Constants for the raw contacts entities table, which can be though of as an outer join - * of the raw_contacts table with the data table. + * <p> + * Constants for the raw contacts entities table, which can be though of as + * an outer join of the raw_contacts table with the data table. It is a strictly + * read-only table. + * </p> + * <p> + * If a raw contact has data rows, the RawContactsEntity cursor will contain + * a one row for each data row. If the raw contact has no data rows, the + * cursor will still contain one row with the raw contact-level information + * and nulls for data columns. + * + * <pre> + * Uri entityUri = ContentUris.withAppendedId(RawContactsEntity.CONTENT_URI, rawContactId); + * Cursor c = getContentResolver().query(entityUri, + * new String[]{ + * RawContactsEntity.SOURCE_ID, + * RawContactsEntity.DATA_ID, + * RawContactsEntity.MIMETYPE, + * RawContactsEntity.DATA1 + * }, null, null, null); + * try { + * while (c.moveToNext()) { + * String sourceId = c.getString(0); + * if (!c.isNull(1)) { + * String mimeType = c.getString(2); + * String data = c.getString(3); + * ... + * } + * } + * } finally { + * c.close(); + * } + * </pre> + * + * <h3>Columns</h3> + * RawContactsEntity has a combination of RawContact and Data columns. + * + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>RawContacts</th> + * </tr> + * <tr> + * <td style="width: 7em;">long</td> + * <td style="width: 20em;">{@link #_ID}</td> + * <td style="width: 5em;">read-only</td> + * <td>Raw contact row ID. See {@link RawContacts}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #CONTACT_ID}</td> + * <td>read-only</td> + * <td>See {@link RawContacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #AGGREGATION_MODE}</td> + * <td>read-only</td> + * <td>See {@link RawContacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #DELETED}</td> + * <td>read-only</td> + * <td>See {@link RawContacts}.</td> + * </tr> + * </table> + * + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Data</th> + * </tr> + * <tr> + * <td style="width: 7em;">long</td> + * <td style="width: 20em;">{@link #DATA_ID}</td> + * <td style="width: 5em;">read-only</td> + * <td>Data row ID. It will be null if the raw contact has no data rows.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #MIMETYPE}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Data}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #IS_PRIMARY}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Data}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #IS_SUPER_PRIMARY}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Data}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #DATA_VERSION}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Data}.</td> + * </tr> + * <tr> + * <td>Any type</td> + * <td> + * {@link #DATA1}<br> + * {@link #DATA2}<br> + * {@link #DATA3}<br> + * {@link #DATA4}<br> + * {@link #DATA5}<br> + * {@link #DATA6}<br> + * {@link #DATA7}<br> + * {@link #DATA8}<br> + * {@link #DATA9}<br> + * {@link #DATA10}<br> + * {@link #DATA11}<br> + * {@link #DATA12}<br> + * {@link #DATA13}<br> + * {@link #DATA14}<br> + * {@link #DATA15} + * </td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Data}.</td> + * </tr> + * <tr> + * <td>Any type</td> + * <td> + * {@link #SYNC1}<br> + * {@link #SYNC2}<br> + * {@link #SYNC3}<br> + * {@link #SYNC4} + * </td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Data}.</td> + * </tr> + * </table> */ public final static class RawContactsEntity implements BaseColumns, DataColumns, RawContactsColumns { @@ -938,6 +2196,9 @@ public final class ContactsContract { public static final String DATA_ID = "data_id"; } + /** + * @see PhoneLookup + */ protected interface PhoneLookupColumns { /** * The phone number as the user entered it. @@ -961,7 +2222,112 @@ public final class ContactsContract { /** * A table that represents the result of looking up a phone number, for * example for caller ID. To perform a lookup you must append the number you - * want to find to {@link #CONTENT_FILTER_URI}. + * want to find to {@link #CONTENT_FILTER_URI}. This query is highly + * optimized. + * <pre> + * Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)); + * resolver.query(uri, new String[]{PhoneLookup.DISPLAY_NAME,... + * </pre> + * + * <h3>Columns</h3> + * + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>PhoneLookup</th> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #_ID}</td> + * <td>read-only</td> + * <td>Data row ID.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #NUMBER}</td> + * <td>read-only</td> + * <td>Phone number.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #TYPE}</td> + * <td>read-only</td> + * <td>Phone number type. See {@link CommonDataKinds.Phone}.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>read-only</td> + * <td>Custom label for the phone number. See {@link CommonDataKinds.Phone}.</td> + * </tr> + * </table> + * <p> + * Columns from the Contacts table are also available through a join. + * </p> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Join with {@link Contacts}</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LOOKUP_KEY}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #DISPLAY_NAME}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #PHOTO_ID}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #IN_VISIBLE_GROUP}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #HAS_PHONE_NUMBER}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TIMES_CONTACTED}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #LAST_TIME_CONTACTED}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #STARRED}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CUSTOM_RINGTONE}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SEND_TO_VOICEMAIL}</td> + * <td>read-only</td> + * <td>See {@link ContactsContract.Contacts}.</td> + * </tr> + * </table> */ public static final class PhoneLookup implements BaseColumns, PhoneLookupColumns, ContactsColumns, ContactOptionsColumns { @@ -973,10 +2339,9 @@ public final class ContactsContract { /** * The content:// style URI for this table. Append the phone number you want to lookup * to this URI and query it to perform a lookup. For example: - * - * {@code - * Uri lookupUri = Uri.withAppendedPath(PhoneLookup.CONTENT_URI, phoneNumber); - * } + * <pre> + * Uri lookupUri = Uri.withAppendedPath(PhoneLookup.CONTENT_URI, Uri.encode(phoneNumber)); + * </pre> */ public static final Uri CONTENT_FILTER_URI = Uri.withAppendedPath(AUTHORITY_URI, "phone_lookup"); @@ -985,6 +2350,8 @@ public final class ContactsContract { /** * Additional data mixed in with {@link StatusColumns} to link * back to specific {@link ContactsContract.Data#_ID} entries. + * + * @see StatusUpdates */ protected interface PresenceColumns { @@ -995,6 +2362,7 @@ public final class ContactsContract { public static final String DATA_ID = "presence_data_id"; /** + * See {@link CommonDataKinds.Im} for a list of defined protocol constants. * <p>Type: NUMBER</p> */ public static final String PROTOCOL = "protocol"; @@ -1024,11 +2392,132 @@ public final class ContactsContract { } /** - * A status update is linked to a {@link Data} row and captures the user's latest status - * update via the corresponding source, e.g. "Having lunch" via "Google Talk". + * <p> + * A status update is linked to a {@link ContactsContract.Data} row and captures + * the user's latest status update via the corresponding source, e.g. + * "Having lunch" via "Google Talk". + * </p> + * <p> + * There are two ways a status update can be inserted: by explicitly linking + * it to a Data row using {@link #DATA_ID} or indirectly linking it to a data row + * using a combination of {@link #PROTOCOL} (or {@link #CUSTOM_PROTOCOL}) and + * {@link #IM_HANDLE}. There is no difference between insert and update, you can use + * either. + * </p> + * <p> + * You cannot use {@link ContentResolver#update} to change a status, but + * {@link ContentResolver#insert} will replace the latests status if it already + * exists. + * </p> + * <p> + * Use {@link ContentResolver#bulkInsert(Uri, ContentValues[])} to insert/update statuses + * for multiple contacts at once. + * </p> + * + * <h3>Columns</h3> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>StatusUpdates</th> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #DATA_ID}</td> + * <td>read/write</td> + * <td>Reference to the {@link Data#_ID} entry that owns this presence. If this + * field is <i>not</i> specified, the provider will attempt to find a data row + * that matches the {@link #PROTOCOL} (or {@link #CUSTOM_PROTOCOL}) and + * {@link #IM_HANDLE} columns. + * </td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #PROTOCOL}</td> + * <td>read/write</td> + * <td>See {@link CommonDataKinds.Im} for a list of defined protocol constants.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CUSTOM_PROTOCOL}</td> + * <td>read/write</td> + * <td>Name of the custom protocol. Should be supplied along with the {@link #PROTOCOL} value + * {@link ContactsContract.CommonDataKinds.Im#PROTOCOL_CUSTOM}. Should be null or + * omitted if {@link #PROTOCOL} value is not + * {@link ContactsContract.CommonDataKinds.Im#PROTOCOL_CUSTOM}.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #IM_HANDLE}</td> + * <td>read/write</td> + * <td> The IM handle the presence item is for. The handle is scoped to + * {@link #PROTOCOL}.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #IM_ACCOUNT}</td> + * <td>read/write</td> + * <td>The IM account for the local user that the presence data came from.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #PRESENCE}</td> + * <td>read/write</td> + * <td>Contact IM presence status. The allowed values are: + * <p> + * <ul> + * <li>{@link #OFFLINE}</li> + * <li>{@link #INVISIBLE}</li> + * <li>{@link #AWAY}</li> + * <li>{@link #IDLE}</li> + * <li>{@link #DO_NOT_DISTURB}</li> + * <li>{@link #AVAILABLE}</li> + * </ul> + * </p> + * <p> + * Since presence status is inherently volatile, the content provider + * may choose not to store this field in long-term storage. + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #STATUS}</td> + * <td>read/write</td> + * <td>Contact's latest status update, e.g. "having toast for breakfast"</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #STATUS_TIMESTAMP}</td> + * <td>read/write</td> + * <td>The absolute time in milliseconds when the status was + * entered by the user. If this value is not provided, the provider will follow + * this logic: if there was no prior status update, the value will be left as null. + * If there was a prior status update, the provider will default this field + * to the current time.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #STATUS_RES_PACKAGE}</td> + * <td>read/write</td> + * <td> The package containing resources for this status: label and icon.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #STATUS_LABEL}</td> + * <td>read/write</td> + * <td>The resource ID of the label describing the source of contact status, + * e.g. "Google Talk". This resource is scoped by the + * {@link #STATUS_RES_PACKAGE}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #STATUS_ICON}</td> + * <td>read/write</td> + * <td>The resource ID of the icon for the source of contact status. This + * resource is scoped by the {@link #STATUS_RES_PACKAGE}.</td> + * </tr> + * </table> */ - // TODO make final as soon as Presence is removed - public static /*final*/ class StatusUpdates implements StatusColumns, PresenceColumns { + public static class StatusUpdates implements StatusColumns, PresenceColumns { /** * This utility class cannot be instantiated @@ -1088,13 +2577,17 @@ public final class ContactsContract { public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/status-update"; } + /** + * @deprecated This old name was never meant to be made public. Do not use. + */ @Deprecated public static final class Presence extends StatusUpdates { } /** - * Container for definitions of common data types stored in the {@link Data} table. + * Container for definitions of common data types stored in the {@link ContactsContract.Data} + * table. */ public static final class CommonDataKinds { /** @@ -1144,7 +2637,69 @@ public final class ContactsContract { } /** - * Parts of the name. + * A data kind representing the contact's proper name. You can use all + * columns defined for {@link ContactsContract.Data} as well as the following aliases. + * + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th><th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #DISPLAY_NAME}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #GIVEN_NAME}</td> + * <td>{@link #DATA2}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #FAMILY_NAME}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #PREFIX}</td> + * <td>{@link #DATA4}</td> + * <td>Common prefixes in English names are "Mr", "Ms", "Dr" etc.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #MIDDLE_NAME}</td> + * <td>{@link #DATA5}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SUFFIX}</td> + * <td>{@link #DATA6}</td> + * <td>Common suffixes in English names are "Sr", "Jr", "III" etc.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #PHONETIC_GIVEN_NAME}</td> + * <td>{@link #DATA7}</td> + * <td>Used for phonetic spelling of the name, e.g. Pinyin, Katakana, Hiragana</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #PHONETIC_MIDDLE_NAME}</td> + * <td>{@link #DATA8}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #PHONETIC_FAMILY_NAME}</td> + * <td>{@link #DATA9}</td> + * <td></td> + * </tr> + * </table> */ public static final class StructuredName implements DataColumnsWithJoins { /** @@ -1213,7 +2768,68 @@ public final class ContactsContract { } /** - * A nickname. + * <p>A data kind representing the contact's nickname. For example, for + * Bob Parr ("Mr. Incredible"): + * <pre> + * ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); + * ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) + * .withValue(Data.RAW_CONTACT_ID, rawContactId) + * .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + * .withValue(StructuredName.DISPLAY_NAME, "Bob Parr") + * .build()); + * + * ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) + * .withValue(Data.RAW_CONTACT_ID, rawContactId) + * .withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE) + * .withValue(Nickname.NAME, "Mr. Incredible") + * .withValue(Nickname.TYPE, Nickname.TYPE_CUSTOM) + * .withValue(Nickname.LABEL, "Superhero") + * .build()); + * + * getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + * </pre> + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as well as the + * following aliases. + * </p> + * + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th><th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #NAME}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td> + * Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_DEFAULT}</li> + * <li>{@link #TYPE_OTHER_NAME}</li> + * <li>{@link #TYPE_MAINDEN_NAME}</li> + * <li>{@link #TYPE_SHORT_NAME}</li> + * <li>{@link #TYPE_INITIALS}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * </table> */ public static final class Nickname implements DataColumnsWithJoins, CommonColumns { /** @@ -1237,7 +2853,64 @@ public final class ContactsContract { } /** - * Common data definition for telephone numbers. + * <p> + * A data kind representing a telephone number. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #NUMBER}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_HOME}</li> + * <li>{@link #TYPE_MOBILE}</li> + * <li>{@link #TYPE_WORK}</li> + * <li>{@link #TYPE_FAX_WORK}</li> + * <li>{@link #TYPE_FAX_HOME}</li> + * <li>{@link #TYPE_PAGER}</li> + * <li>{@link #TYPE_OTHER}</li> + * <li>{@link #TYPE_CALLBACK}</li> + * <li>{@link #TYPE_CAR}</li> + * <li>{@link #TYPE_COMPANY_MAIN}</li> + * <li>{@link #TYPE_ISDN}</li> + * <li>{@link #TYPE_MAIN}</li> + * <li>{@link #TYPE_OTHER_FAX}</li> + * <li>{@link #TYPE_RADIO}</li> + * <li>{@link #TYPE_TELEX}</li> + * <li>{@link #TYPE_TTY_TDD}</li> + * <li>{@link #TYPE_WORK_MOBILE}</li> + * <li>{@link #TYPE_WORK_PAGER}</li> + * <li>{@link #TYPE_ASSISTANT}</li> + * <li>{@link #TYPE_MMS}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * </table> */ public static final class Phone implements DataColumnsWithJoins, CommonColumns { /** @@ -1365,7 +3038,48 @@ public final class ContactsContract { } /** - * Common data definition for email addresses. + * <p> + * A data kind representing an email address. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #DATA}</td> + * <td>{@link #DATA1}</td> + * <td>Email address itself.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_HOME}</li> + * <li>{@link #TYPE_WORK}</li> + * <li>{@link #TYPE_OTHER}</li> + * <li>{@link #TYPE_MOBILE}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * </table> */ public static final class Email implements DataColumnsWithJoins, CommonColumns { /** @@ -1390,22 +3104,50 @@ public final class ContactsContract { "emails"); /** + * <p> * The content:// style URL for looking up data rows by email address. The * lookup argument, an email address, should be passed as an additional path segment * after this URI. + * </p> + * <p>Example: + * <pre> + * Uri uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(email)); + * Cursor c = getContentResolver().query(uri, + * new String[]{Email.CONTACT_ID, Email.DISPLAY_NAME, Email.DATA}, + * null, null, null); + * </pre> + * </p> */ public static final Uri CONTENT_LOOKUP_URI = Uri.withAppendedPath(CONTENT_URI, "lookup"); /** + * <p> * The content:// style URL for email lookup using a filter. The filter returns * records of MIME type {@link #CONTENT_ITEM_TYPE}. The filter is applied * to display names as well as email addresses. The filter argument should be passed * as an additional path segment after this URI. + * </p> + * <p>The query in the following example will return "Robert Parr (bob@incredibles.com)" + * as well as "Bob Parr (incredible@android.com)". + * <pre> + * Uri uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode("bob")); + * Cursor c = getContentResolver().query(uri, + * new String[]{Email.DISPLAY_NAME, Email.DATA}, + * null, null, null); + * </pre> + * </p> */ public static final Uri CONTENT_FILTER_URI = Uri.withAppendedPath(CONTENT_URI, "filter"); + /** + * The email address. + * <P>Type: TEXT</P> + * @hide TODO: Unhide in a separate CL + */ + public static final String ADDRESS = DATA1; + public static final int TYPE_HOME = 1; public static final int TYPE_WORK = 2; public static final int TYPE_OTHER = 3; @@ -1448,7 +3190,89 @@ public final class ContactsContract { } /** - * Common data definition for postal addresses. + * <p> + * A data kind representing a postal addresses. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #FORMATTED_ADDRESS}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_HOME}</li> + * <li>{@link #TYPE_WORK}</li> + * <li>{@link #TYPE_OTHER}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #STREET}</td> + * <td>{@link #DATA4}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #POBOX}</td> + * <td>{@link #DATA5}</td> + * <td>Post Office Box number</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #NEIGHBORHOOD}</td> + * <td>{@link #DATA6}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CITY}</td> + * <td>{@link #DATA7}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #REGION}</td> + * <td>{@link #DATA8}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #POSTCODE}</td> + * <td>{@link #DATA9}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #COUNTRY}</td> + * <td>{@link #DATA10}</td> + * <td></td> + * </tr> + * </table> */ public static final class StructuredPostal implements DataColumnsWithJoins, CommonColumns { /** @@ -1573,7 +3397,76 @@ public final class ContactsContract { } /** - * Common data definition for IM addresses. + * <p> + * A data kind representing an IM address + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #DATA}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_HOME}</li> + * <li>{@link #TYPE_WORK}</li> + * <li>{@link #TYPE_OTHER}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #PROTOCOL}</td> + * <td>{@link #DATA5}</td> + * <td> + * <p> + * Allowed values: + * <ul> + * <li>{@link #PROTOCOL_CUSTOM}. Also provide the actual protocol name + * as {@link #CUSTOM_PROTOCOL}.</li> + * <li>{@link #PROTOCOL_AIM}</li> + * <li>{@link #PROTOCOL_MSN}</li> + * <li>{@link #PROTOCOL_YAHOO}</li> + * <li>{@link #PROTOCOL_SKYPE}</li> + * <li>{@link #PROTOCOL_QQ}</li> + * <li>{@link #PROTOCOL_GOOGLE_TALK}</li> + * <li>{@link #PROTOCOL_ICQ}</li> + * <li>{@link #PROTOCOL_JABBER}</li> + * <li>{@link #PROTOCOL_NETMEETING}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #CUSTOM_PROTOCOL}</td> + * <td>{@link #DATA6}</td> + * <td></td> + * </tr> + * </table> */ public static final class Im implements DataColumnsWithJoins, CommonColumns { /** @@ -1676,7 +3569,82 @@ public final class ContactsContract { } /** - * Common data definition for organizations. + * <p> + * A data kind representing an organization. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #COMPANY}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_WORK}</li> + * <li>{@link #TYPE_OTHER}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #TITLE}</td> + * <td>{@link #DATA4}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #DEPARTMENT}</td> + * <td>{@link #DATA5}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #JOB_DESCRIPTION}</td> + * <td>{@link #DATA6}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SYMBOL}</td> + * <td>{@link #DATA7}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #PHONETIC_NAME}</td> + * <td>{@link #DATA8}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #OFFICE_LOCATION}</td> + * <td>{@link #DATA9}</td> + * <td></td> + * </tr> + * </table> */ public static final class Organization implements DataColumnsWithJoins, CommonColumns { /** @@ -1761,7 +3729,58 @@ public final class ContactsContract { } /** - * Common data definition for relations. + * <p> + * A data kind representing a relation. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #NAME}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_ASSISTANT}</li> + * <li>{@link #TYPE_BROTHER}</li> + * <li>{@link #TYPE_CHILD}</li> + * <li>{@link #TYPE_DOMESTIC_PARTNER}</li> + * <li>{@link #TYPE_FATHER}</li> + * <li>{@link #TYPE_FRIEND}</li> + * <li>{@link #TYPE_MANAGER}</li> + * <li>{@link #TYPE_MOTHER}</li> + * <li>{@link #TYPE_PARENT}</li> + * <li>{@link #TYPE_PARTNER}</li> + * <li>{@link #TYPE_REFERRED_BY}</li> + * <li>{@link #TYPE_RELATIVE}</li> + * <li>{@link #TYPE_SISTER}</li> + * <li>{@link #TYPE_SPOUSE}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * </table> */ public static final class Relation implements DataColumnsWithJoins, CommonColumns { /** @@ -1795,7 +3814,47 @@ public final class ContactsContract { } /** - * Common data definition for events. + * <p> + * A data kind representing an event. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #START_DATE}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_ANNIVERSARY}</li> + * <li>{@link #TYPE_OTHER}</li> + * <li>{@link #TYPE_BIRTHDAY}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * </table> */ public static final class Event implements DataColumnsWithJoins, CommonColumns { /** @@ -1835,7 +3894,33 @@ public final class ContactsContract { } /** - * Photo of the contact. + * <p> + * A data kind representing an photo for the contact. + * </p> + * <p> + * Some sync adapters will choose to download photos in a separate + * pass. A common pattern is to use columns {@link ContactsContract.Data#SYNC1} + * through {@link ContactsContract.Data#SYNC4} to store temporary + * data, e.g. the image URL or ID, state of download, server-side version + * of the image. It is allowed for the {@link #PHOTO} to be null. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>BLOB</td> + * <td>{@link #PHOTO}</td> + * <td>{@link #DATA15}</td> + * <td>By convention, binary data is stored in DATA15.</td> + * </tr> + * </table> */ public static final class Photo implements DataColumnsWithJoins { /** @@ -1856,7 +3941,26 @@ public final class ContactsContract { } /** + * <p> * Notes about the contact. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #NOTE}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * </table> */ public static final class Note implements DataColumnsWithJoins { /** @@ -1875,7 +3979,43 @@ public final class ContactsContract { } /** + * <p> * Group Membership. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #GROUP_ROW_ID}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #GROUP_SOURCE_ID}</td> + * <td>none</td> + * <td> + * <p> + * The sourceid of the group that this group membership refers to. + * Exactly one of this or {@link #GROUP_ROW_ID} must be set when + * inserting a row. + * </p> + * <p> + * If this field is specified, the provider will first try to + * look up a group with this {@link Groups Groups.SOURCE_ID}. If such a group + * is found, it will use the corresponding row id. If the group is not + * found, it will create one. + * </td> + * </tr> + * </table> */ public static final class GroupMembership implements DataColumnsWithJoins { /** @@ -1903,7 +4043,51 @@ public final class ContactsContract { } /** - * Website related to the contact. + * <p> + * A data kind representing a website related to the contact. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #URL}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>{@link #DATA2}</td> + * <td>Allowed values are: + * <p> + * <ul> + * <li>{@link #TYPE_CUSTOM}. Put the actual type in {@link #LABEL}.</li> + * <li>{@link #TYPE_HOMEPAGE}</li> + * <li>{@link #TYPE_BLOG}</li> + * <li>{@link #TYPE_PROFILE}</li> + * <li>{@link #TYPE_HOME}</li> + * <li>{@link #TYPE_WORK}</li> + * <li>{@link #TYPE_FTP}</li> + * <li>{@link #TYPE_OTHER}</li> + * </ul> + * </p> + * </td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #LABEL}</td> + * <td>{@link #DATA3}</td> + * <td></td> + * </tr> + * </table> */ public static final class Website implements DataColumnsWithJoins, CommonColumns { /** @@ -1930,6 +4114,9 @@ public final class ContactsContract { } } + /** + * @see Groups + */ protected interface GroupsColumns { /** * The display title of this group. @@ -2000,11 +4187,11 @@ public final class ContactsContract { /** * The "deleted" flag: "0" by default, "1" if the row has been marked * for deletion. When {@link android.content.ContentResolver#delete} is - * called on a raw contact, it is marked for deletion and removed from its - * aggregate contact. The sync adaptor deletes the raw contact on the server and - * then calls ContactResolver.delete once more, this time setting the the - * {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter to finalize - * the data removal. + * called on a group, it is marked for deletion. The sync adaptor + * deletes the group on the server and then calls ContactResolver.delete + * once more, this time setting the the + * {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter to + * finalize the data removal. * <P>Type: INTEGER</P> */ public static final String DELETED = "deleted"; @@ -2019,7 +4206,82 @@ public final class ContactsContract { } /** - * Constants for the groups table. + * Constants for the groups table. Only per-account groups are supported. + * <h2>Columns</h2> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Groups</th> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #_ID}</td> + * <td>read-only</td> + * <td>Row ID. Sync adapter should try to preserve row IDs during updates. + * In other words, it would be a really bad idea to delete and reinsert a + * group. A sync adapter should always do an update instead.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #TITLE}</td> + * <td>read/write</td> + * <td>The display title of this group.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #NOTES}</td> + * <td>read/write</td> + * <td>Notes about the group.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SYSTEM_ID}</td> + * <td>read/write</td> + * <td>The ID of this group if it is a System Group, i.e. a group that has a + * special meaning to the sync adapter, null otherwise.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SUMMARY_COUNT}</td> + * <td>read-only</td> + * <td>The total number of {@link Contacts} that have + * {@link CommonDataKinds.GroupMembership} in this group. Read-only value + * that is only present when querying {@link Groups#CONTENT_SUMMARY_URI}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SUMMARY_WITH_PHONES}</td> + * <td>read-only</td> + * <td>The total number of {@link Contacts} that have both + * {@link CommonDataKinds.GroupMembership} in this group, and also have + * phone numbers. Read-only value that is only present when querying + * {@link Groups#CONTENT_SUMMARY_URI}.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #GROUP_VISIBLE}</td> + * <td>read-only</td> + * <td>Flag indicating if the contacts belonging to this group should be + * visible in any user interface. Allowed values: 0 and 1.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #DELETED}</td> + * <td>read/write</td> + * <td>The "deleted" flag: "0" by default, "1" if the row has been marked + * for deletion. When {@link android.content.ContentResolver#delete} is + * called on a group, it is marked for deletion. The sync adaptor deletes + * the group on the server and then calls ContactResolver.delete once more, + * this time setting the the {@link ContactsContract#CALLER_IS_SYNCADAPTER} + * query parameter to finalize the data removal.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SHOULD_SYNC}</td> + * <td>read/write</td> + * <td>Whether this group should be synced if the SYNC_EVERYTHING settings + * is false for this group's account.</td> + * </tr> + * </table> */ public static final class Groups implements BaseColumns, GroupsColumns, SyncColumns { /** @@ -2035,7 +4297,7 @@ public final class ContactsContract { /** * The content:// style URI for this table joined with details data from - * {@link Data}. + * {@link ContactsContract.Data}. */ public static final Uri CONTENT_SUMMARY_URI = Uri.withAppendedPath(AUTHORITY_URI, "groups_summary"); @@ -2052,9 +4314,39 @@ public final class ContactsContract { } /** + * <p> * Constants for the contact aggregation exceptions table, which contains - * aggregation rules overriding those used by automatic aggregation. This type only - * supports query and update. Neither insert nor delete are supported. + * aggregation rules overriding those used by automatic aggregation. This + * type only supports query and update. Neither insert nor delete are + * supported. + * </p> + * <h2>Columns</h2> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>AggregationExceptions</th> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #TYPE}</td> + * <td>read/write</td> + * <td>The type of exception: {@link #TYPE_KEEP_TOGETHER}, + * {@link #TYPE_KEEP_SEPARATE} or {@link #TYPE_AUTOMATIC}.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #RAW_CONTACT_ID1}</td> + * <td>read/write</td> + * <td>A reference to the {@link RawContacts#_ID} of the raw contact that + * the rule applies to.</td> + * </tr> + * <tr> + * <td>long</td> + * <td>{@link #RAW_CONTACT_ID2}</td> + * <td>read/write</td> + * <td>A reference to the other {@link RawContacts#_ID} of the raw contact + * that the rule applies to.</td> + * </tr> + * </table> */ public static final class AggregationExceptions implements BaseColumns { /** @@ -2117,6 +4409,9 @@ public final class ContactsContract { public static final String RAW_CONTACT_ID2 = "raw_contact_id2"; } + /** + * @see Settings + */ protected interface SettingsColumns { /** * The name of the account instance to which this row belongs. @@ -2172,8 +4467,68 @@ public final class ContactsContract { } /** - * Contacts-specific settings for various {@link Account}. + * <p> + * Contacts-specific settings for various {@link Account}'s. + * </p> + * <h2>Columns</h2> + * <table class="jd-sumtable"> + * <tr> + * <th colspan='4'>Settings</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #ACCOUNT_NAME}</td> + * <td>read/write-once</td> + * <td>The name of the account instance to which this row belongs.</td> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #ACCOUNT_TYPE}</td> + * <td>read/write-once</td> + * <td>The type of account to which this row belongs, which when paired with + * {@link #ACCOUNT_NAME} identifies a specific account.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #SHOULD_SYNC}</td> + * <td>read/write</td> + * <td>Depending on the mode defined by the sync-adapter, this flag controls + * the top-level sync behavior for this data source.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #UNGROUPED_VISIBLE}</td> + * <td>read/write</td> + * <td>Flag indicating if contacts without any + * {@link CommonDataKinds.GroupMembership} entries should be visible in any + * user interface.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #ANY_UNSYNCED}</td> + * <td>read-only</td> + * <td>Read-only flag indicating if this {@link #SHOULD_SYNC} or any + * {@link Groups#SHOULD_SYNC} under this account have been marked as + * unsynced.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #UNGROUPED_COUNT}</td> + * <td>read-only</td> + * <td>Read-only count of {@link Contacts} from a specific source that have + * no {@link CommonDataKinds.GroupMembership} entries.</td> + * </tr> + * <tr> + * <td>int</td> + * <td>{@link #UNGROUPED_WITH_PHONES}</td> + * <td>read-only</td> + * <td>Read-only count of {@link Contacts} from a specific source that have + * no {@link CommonDataKinds.GroupMembership} entries, and also have phone + * numbers.</td> + * </tr> + * </table> */ + public static final class Settings implements SettingsColumns { /** * This utility class cannot be instantiated @@ -2252,8 +4607,8 @@ public final class ContactsContract { /** * Trigger a dialog that lists the various methods of interacting with * the requested {@link Contacts} entry. This may be based on available - * {@link Data} rows under that contact, and may also include social - * status and presence details. + * {@link ContactsContract.Data} rows under that contact, and may also + * include social status and presence details. * * @param context The parent {@link Context} that may be used as the * parent for this dialog. @@ -2291,8 +4646,8 @@ public final class ContactsContract { /** * Trigger a dialog that lists the various methods of interacting with * the requested {@link Contacts} entry. This may be based on available - * {@link Data} rows under that contact, and may also include social - * status and presence details. + * {@link ContactsContract.Data} rows under that contact, and may also + * include social status and presence details. * * @param context The parent {@link Context} that may be used as the * parent for this dialog. @@ -2593,7 +4948,7 @@ public final class ContactsContract { /** * The extra field for the contact phone number type. * <P>Type: Either an integer value from - * {@link android.provider.Contacts.PhonesColumns PhonesColumns}, + * {@link CommonDataKinds.Phone}, * or a string specifying a custom label.</P> */ public static final String PHONE_TYPE = "phone_type"; @@ -2613,7 +4968,7 @@ public final class ContactsContract { /** * The extra field for an optional second contact phone number type. * <P>Type: Either an integer value from - * {@link android.provider.Contacts.PhonesColumns PhonesColumns}, + * {@link CommonDataKinds.Phone}, * or a string specifying a custom label.</P> */ public static final String SECONDARY_PHONE_TYPE = "secondary_phone_type"; @@ -2627,7 +4982,7 @@ public final class ContactsContract { /** * The extra field for an optional third contact phone number type. * <P>Type: Either an integer value from - * {@link android.provider.Contacts.PhonesColumns PhonesColumns}, + * {@link CommonDataKinds.Phone}, * or a string specifying a custom label.</P> */ public static final String TERTIARY_PHONE_TYPE = "tertiary_phone_type"; @@ -2641,7 +4996,7 @@ public final class ContactsContract { /** * The extra field for the contact email type. * <P>Type: Either an integer value from - * {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} + * {@link CommonDataKinds.Email} * or a string specifying a custom label.</P> */ public static final String EMAIL_TYPE = "email_type"; @@ -2661,7 +5016,7 @@ public final class ContactsContract { /** * The extra field for an optional second contact email type. * <P>Type: Either an integer value from - * {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} + * {@link CommonDataKinds.Email} * or a string specifying a custom label.</P> */ public static final String SECONDARY_EMAIL_TYPE = "secondary_email_type"; @@ -2675,7 +5030,7 @@ public final class ContactsContract { /** * The extra field for an optional third contact email type. * <P>Type: Either an integer value from - * {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} + * {@link CommonDataKinds.Email} * or a string specifying a custom label.</P> */ public static final String TERTIARY_EMAIL_TYPE = "tertiary_email_type"; @@ -2689,7 +5044,7 @@ public final class ContactsContract { /** * The extra field for the contact postal address type. * <P>Type: Either an integer value from - * {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} + * {@link CommonDataKinds.StructuredPostal} * or a string specifying a custom label.</P> */ public static final String POSTAL_TYPE = "postal_type"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cb3dc16..f7e55db 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1409,6 +1409,13 @@ public final class Settings { public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions"; /** + * Whether the notification LED should repeatedly flash when a notification is + * pending. The value is boolean (1 or 0). + * @hide + */ + public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * @hide @@ -1462,7 +1469,8 @@ public final class Settings { TTY_MODE, SOUND_EFFECTS_ENABLED, HAPTIC_FEEDBACK_ENABLED, - SHOW_WEB_SUGGESTIONS + SHOW_WEB_SUGGESTIONS, + NOTIFICATION_LIGHT_PULSE }; // Settings moved to Settings.Secure @@ -3638,6 +3646,19 @@ public final class Settings { public static final String LAST_KMSG_KB = "last_kmsg_kb"; /** + * The length of time in milli-seconds that automatic small adjustments to + * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded. + */ + public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing"; + + /** + * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment + * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been + * exceeded. + */ + public static final String NITZ_UPDATE_DIFF = "nitz_update_diff"; + + /** * @deprecated * @hide */ diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index ec3b2ff..1742e72 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -96,15 +96,16 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { BluetoothDevice.ERROR); switch(bondState) { case BluetoothDevice.BOND_BONDED: - setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO); + 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)) { - if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF && + if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT && isSinkDevice(device)) { // This device is a preferred sink. Make an A2DP connection // after a delay. We delay to avoid connection collisions, @@ -171,7 +172,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { // check bluetooth is still on, device is still preferred, and // nothing is currently connected if (mBluetoothService.isEnabled() && - getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF && + getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT && lookupSinksMatchingStates(new int[] { BluetoothA2dp.STATE_CONNECTING, BluetoothA2dp.STATE_CONNECTED, @@ -376,6 +377,16 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return sinks.toArray(new BluetoothDevice[sinks.size()]); } + public synchronized BluetoothDevice[] getNonDisconnectedSinks() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Set<BluetoothDevice> sinks = lookupSinksMatchingStates( + new int[] {BluetoothA2dp.STATE_CONNECTED, + BluetoothA2dp.STATE_PLAYING, + BluetoothA2dp.STATE_CONNECTING, + BluetoothA2dp.STATE_DISCONNECTING}); + return sinks.toArray(new BluetoothDevice[sinks.size()]); + } + public synchronized int getSinkState(BluetoothDevice device) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Integer state = mAudioDevices.get(device); @@ -388,7 +399,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), - BluetoothA2dp.PRIORITY_OFF); + BluetoothA2dp.PRIORITY_UNDEFINED); } public synchronized boolean setSinkPriority(BluetoothDevice device, int priority) { @@ -447,9 +458,14 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { checkSinkSuspendState(state); mTargetA2dpState = -1; - if (state == BluetoothA2dp.STATE_CONNECTING) { - mAudioManager.setParameters("A2dpSuspended=false"); + if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF && + state == BluetoothA2dp.STATE_CONNECTING || + state == BluetoothA2dp.STATE_CONNECTED) { + // We have connected or attempting to connect. + // Bump priority + setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); } + Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState); diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index c0b9a68..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 @@ -292,9 +293,9 @@ class BluetoothEventLoop { mBluetoothService.setProperty(name, propValues[1]); } else if (name.equals("Pairable") || name.equals("Discoverable")) { String pairable = name.equals("Pairable") ? propValues[1] : - mBluetoothService.getProperty("Pairable"); + mBluetoothService.getPropertyInternal("Pairable"); String discoverable = name.equals("Discoverable") ? propValues[1] : - mBluetoothService.getProperty("Discoverable"); + mBluetoothService.getPropertyInternal("Discoverable"); // This shouldn't happen, unless Adapter Properties are null. if (pairable == null || discoverable == null) @@ -492,6 +493,14 @@ class BluetoothEventLoop { mBluetoothService.getBondState().getPendingOutgoingBonding(); if (address.equals(pendingOutgoingAddress)) { // we initiated the bonding + + // Check if its a dock + if (mBluetoothService.isBluetoothDock(address)) { + String pin = mBluetoothService.getDockPin(); + mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin)); + return; + } + BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address)); // try 0000 once if the device looks dumb @@ -538,12 +547,14 @@ class BluetoothEventLoop { boolean authorized = false; ParcelUuid uuid = ParcelUuid.fromString(deviceUuid); + BluetoothA2dp a2dp = new BluetoothA2dp(mContext); + // Bluez sends the UUID of the local service being accessed, _not_ the // remote service if (mBluetoothService.isEnabled() && (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) - || BluetoothUuid.isAdvAudioDist(uuid))) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); + || BluetoothUuid.isAdvAudioDist(uuid)) && + !isOtherSinkInNonDisconnectingState(address)) { BluetoothDevice device = mAdapter.getRemoteDevice(address); authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; if (authorized) { @@ -558,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 d1dd311..018f7d7 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -32,16 +32,17 @@ import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetooth; import android.bluetooth.IBluetoothCallback; -import android.os.ParcelUuid; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.os.Binder; -import android.os.IBinder; import android.os.Handler; +import android.os.IBinder; import android.os.Message; +import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemService; @@ -50,7 +51,13 @@ import android.util.Log; import com.android.internal.app.IBatteryStats; +import java.io.BufferedInputStream; +import java.io.BufferedWriter; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; @@ -58,6 +65,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Random; public class BluetoothService extends IBluetooth.Stub { private static final String TAG = "BluetoothService"; @@ -65,7 +73,6 @@ public class BluetoothService extends IBluetooth.Stub { private int mNativeData; private BluetoothEventLoop mEventLoop; - private IntentFilter mIntentFilter; private boolean mIsAirplaneSensitive; private int mBluetoothState; private boolean mRestart = false; // need to call enable() after disable() @@ -79,6 +86,12 @@ public class BluetoothService extends IBluetooth.Stub { private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + private static final String DOCK_ADDRESS_PATH = "/sys/class/switch/dock/bt_addr"; + private static final String DOCK_PIN_PATH = "/sys/class/switch/dock/bt_pin"; + + private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address"; + private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings"; + private static final int MESSAGE_REGISTER_SDP_RECORDS = 1; private static final int MESSAGE_FINISH_DISABLE = 2; private static final int MESSAGE_UUID_INTENT = 3; @@ -104,6 +117,9 @@ public class BluetoothService extends IBluetooth.Stub { private final HashMap<Integer, Integer> mServiceRecordToPid; + private static String mDockAddress; + private String mDockPin; + private static class RemoteService { public String address; public ParcelUuid uuid; @@ -150,7 +166,79 @@ public class BluetoothService extends IBluetooth.Stub { mUuidIntentTracker = new ArrayList<String>(); mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>(); mServiceRecordToPid = new HashMap<Integer, Integer>(); - registerForAirplaneMode(); + + IntentFilter filter = new IntentFilter(); + registerForAirplaneMode(filter); + + filter.addAction(Intent.ACTION_DOCK_EVENT); + mContext.registerReceiver(mReceiver, filter); + } + + public static synchronized String readDockBluetoothAddress() { + if (mDockAddress != null) return mDockAddress; + + BufferedInputStream file = null; + String dockAddress; + try { + file = new BufferedInputStream(new FileInputStream(DOCK_ADDRESS_PATH)); + byte[] address = new byte[17]; + file.read(address); + dockAddress = new String(address); + dockAddress = dockAddress.toUpperCase(); + if (BluetoothAdapter.checkBluetoothAddress(dockAddress)) { + mDockAddress = dockAddress; + return mDockAddress; + } else { + log("CheckBluetoothAddress failed for car dock address:" + dockAddress); + } + } catch (FileNotFoundException e) { + log("FileNotFoundException while trying to read dock address"); + } catch (IOException e) { + log("IOException while trying to read dock address"); + } finally { + if (file != null) { + try { + file.close(); + } catch (IOException e) { + // Ignore + } + } + } + mDockAddress = null; + return null; + } + + private synchronized boolean writeDockPin() { + BufferedWriter out = null; + try { + out = new BufferedWriter(new FileWriter(DOCK_PIN_PATH)); + + // Generate a random 4 digit pin between 0000 and 9999 + // This is not truly random but good enough for our purposes. + int pin = (int) Math.floor(Math.random() * 10000); + + mDockPin = String.format("%04d", pin); + out.write(mDockPin); + return true; + } catch (FileNotFoundException e) { + log("FileNotFoundException while trying to write dock pairing pin"); + } catch (IOException e) { + log("IOException while while trying to write dock pairing pin"); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // Ignore + } + } + } + mDockPin = null; + return false; + } + + /*package*/ synchronized String getDockPin() { + return mDockPin; } public synchronized void initAfterRegistration() { @@ -160,9 +248,7 @@ public class BluetoothService extends IBluetooth.Stub { @Override protected void finalize() throws Throwable { - if (mIsAirplaneSensitive) { - mContext.unregisterReceiver(mReceiver); - } + mContext.unregisterReceiver(mReceiver); try { cleanupNativeDataNative(); } finally { @@ -172,6 +258,10 @@ public class BluetoothService extends IBluetooth.Stub { public boolean isEnabled() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + return isEnabledInternal(); + } + + private boolean isEnabledInternal() { return mBluetoothState == BluetoothAdapter.STATE_ON; } @@ -328,7 +418,7 @@ public class BluetoothService extends IBluetooth.Stub { public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_REGISTER_SDP_RECORDS: - if (!isEnabled()) { + if (!isEnabledInternal()) { return; } // SystemService.start() forks sdptool to register service @@ -340,14 +430,14 @@ public class BluetoothService extends IBluetooth.Stub { // records, use a DBUS call instead. switch (msg.arg1) { case 1: - Log.d(TAG, "Registering hsag record"); - SystemService.start("hsag"); + Log.d(TAG, "Registering hfag record"); + SystemService.start("hfag"); mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 2, -1), 500); break; case 2: - Log.d(TAG, "Registering hfag record"); - SystemService.start("hfag"); + Log.d(TAG, "Registering hsag record"); + SystemService.start("hsag"); mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 3, -1), 500); break; @@ -375,7 +465,7 @@ public class BluetoothService extends IBluetooth.Stub { break; case MESSAGE_DISCOVERABLE_TIMEOUT: int mode = msg.arg1; - if (isEnabled()) { + if (isEnabledInternal()) { // TODO: Switch back to the previous scan mode // This is ok for now, because we only use // CONNECTABLE and CONNECTABLE_DISCOVERABLE @@ -502,10 +592,14 @@ public class BluetoothService extends IBluetooth.Stub { // List of names of Bluetooth devices for which auto pairing should be // disabled. - private final ArrayList<String> mAutoPairingNameBlacklist = + private final ArrayList<String> mAutoPairingExactNameBlacklist = new ArrayList<String>(Arrays.asList( "Motorola IHF1000", "i.TechBlueBAND", "X5 Stereo v1.3")); + private final ArrayList<String> mAutoPairingPartialNameBlacklist = + new ArrayList<String>(Arrays.asList( + "BMW", "Audi")); + // If this is an outgoing connection, store the address. // There can be only 1 pending outgoing connection at a time, private String mPendingOutgoingBonding; @@ -523,7 +617,7 @@ public class BluetoothService extends IBluetooth.Stub { return; } String []bonds = null; - String val = getProperty("Devices"); + String val = getPropertyInternal("Devices"); if (val != null) { bonds = val.split(","); } @@ -585,9 +679,13 @@ public class BluetoothService extends IBluetooth.Stub { String name = getRemoteName(address); if (name != null) { - for (String blacklistName : mAutoPairingNameBlacklist) { + for (String blacklistName : mAutoPairingExactNameBlacklist) { if (name.equals(blacklistName)) return true; } + + for (String blacklistName : mAutoPairingPartialNameBlacklist) { + if (name.startsWith(blacklistName)) return true; + } } return false; } @@ -667,6 +765,7 @@ public class BluetoothService extends IBluetooth.Stub { } /*package*/synchronized void getAllProperties() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); mAdapterProperties.clear(); @@ -726,16 +825,19 @@ public class BluetoothService extends IBluetooth.Stub { // The following looks dirty. private boolean setPropertyString(String key, String value) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return false; return setAdapterPropertyStringNative(key, value); } private boolean setPropertyInteger(String key, int value) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return false; return setAdapterPropertyIntegerNative(key, value); } private boolean setPropertyBoolean(String key, boolean value) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return false; return setAdapterPropertyBooleanNative(key, value ? 1 : 0); } @@ -789,7 +891,12 @@ public class BluetoothService extends IBluetooth.Stub { return true; } - /*package*/ synchronized String getProperty (String name) { + /*package*/ synchronized String getProperty(String name) { + if (!isEnabledInternal()) return null; + return getPropertyInternal(name); + } + + /*package*/ synchronized String getPropertyInternal(String name) { if (!mAdapterProperties.isEmpty()) return mAdapterProperties.get(name); getAllProperties(); @@ -844,7 +951,7 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized int getScanMode() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabled()) + if (!isEnabledInternal()) return BluetoothAdapter.SCAN_MODE_NONE; boolean pairable = getProperty("Pairable").equals("true"); @@ -855,15 +962,16 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean startDiscovery() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - if (!isEnabled()) { - return false; - } + if (!isEnabledInternal()) return false; + return startDiscoveryNative(); } public synchronized boolean cancelDiscovery() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + return stopDiscoveryNative(); } @@ -879,6 +987,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean createBond(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + if (!BluetoothAdapter.checkBluetoothAddress(address)) { return false; } @@ -898,6 +1008,13 @@ public class BluetoothService extends IBluetooth.Stub { return false; } + if (address.equals(mDockAddress)) { + if (!writeDockPin()) { + log("Error while writing Pin for the dock"); + return false; + } + } + if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) { return false; } @@ -911,6 +1028,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean cancelBondProcess(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + if (!BluetoothAdapter.checkBluetoothAddress(address)) { return false; } @@ -928,6 +1047,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean removeBond(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + if (!BluetoothAdapter.checkBluetoothAddress(address)) { return false; } @@ -947,11 +1068,20 @@ public class BluetoothService extends IBluetooth.Stub { return mBondState.getBondState(address.toUpperCase()); } + public synchronized boolean isBluetoothDock(String address) { + SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, + mContext.MODE_PRIVATE); + + return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address); + } + /*package*/ boolean isRemoteDeviceInCache(String address) { return (mDeviceProperties.get(address) != null); } /*package*/ String[] getRemoteDeviceProperties(String address) { + if (!isEnabledInternal()) return null; + String objectPath = getObjectPathFromAddress(address); return (String [])getDevicePropertiesNative(objectPath); } @@ -1047,6 +1177,8 @@ public class BluetoothService extends IBluetooth.Stub { return false; } + if (!isEnabledInternal()) return false; + return setDevicePropertyBooleanNative(getObjectPathFromAddress(address), "Trusted", value ? 1 : 0); } @@ -1136,6 +1268,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean fetchRemoteUuids(String address, ParcelUuid uuid, IBluetoothCallback callback) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return false; + if (!BluetoothAdapter.checkBluetoothAddress(address)) { return false; } @@ -1190,6 +1324,8 @@ public class BluetoothService extends IBluetooth.Stub { */ public int getRemoteServiceChannel(String address, ParcelUuid uuid) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return -1; + if (!BluetoothAdapter.checkBluetoothAddress(address)) { return BluetoothDevice.ERROR; } @@ -1208,6 +1344,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean setPin(String address, byte[] pin) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + if (pin == null || pin.length <= 0 || pin.length > 16 || !BluetoothAdapter.checkBluetoothAddress(address)) { return false; @@ -1234,6 +1372,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean setPasskey(String address, int passkey) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + if (passkey < 0 || passkey > 999999 || !BluetoothAdapter.checkBluetoothAddress(address)) { return false; } @@ -1251,6 +1391,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean setPairingConfirmation(String address, boolean confirm) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + address = address.toUpperCase(); Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); if (data == null) { @@ -1265,6 +1407,8 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean cancelPairingUserInput(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + if (!BluetoothAdapter.checkBluetoothAddress(address)) { return false; } @@ -1281,7 +1425,7 @@ public class BluetoothService extends IBluetooth.Stub { return cancelPairingUserInputNative(address, data.intValue()); } - public void updateDeviceServiceChannelCache(String address) { + /*package*/ void updateDeviceServiceChannelCache(String address) { ParcelUuid[] deviceUuids = getRemoteUuids(address); // We are storing the rfcomm channel numbers only for the uuids // we are interested in. @@ -1356,8 +1500,9 @@ public class BluetoothService extends IBluetooth.Stub { */ public synchronized int addRfcommServiceRecord(String serviceName, ParcelUuid uuid, int channel, IBinder b) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return -1; + if (serviceName == null || uuid == null || channel < 1 || channel > BluetoothSocket.MAX_RFCOMM_CHANNEL) { return -1; @@ -1417,6 +1562,8 @@ public class BluetoothService extends IBluetooth.Stub { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (intent == null) return; + String action = intent.getAction(); if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { ContentResolver resolver = context.getContentResolver(); @@ -1431,18 +1578,31 @@ public class BluetoothService extends IBluetooth.Stub { disable(false); } } + } else if (Intent.ACTION_DOCK_EVENT.equals(action)) { + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state); + if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + mDockAddress = null; + mDockPin = null; + } else { + SharedPreferences.Editor editor = + mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, + mContext.MODE_PRIVATE).edit(); + editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true); + editor.commit(); + } } } }; - private void registerForAirplaneMode() { + private void registerForAirplaneMode(IntentFilter filter) { String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_RADIOS); mIsAirplaneSensitive = airplaneModeRadios == null ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH); if (mIsAirplaneSensitive) { - mIntentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); - mContext.registerReceiver(mReceiver, mIntentFilter); + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); } } @@ -1598,7 +1758,7 @@ public class BluetoothService extends IBluetooth.Stub { } /*package*/ String getAddressFromObjectPath(String objectPath) { - String adapterObjectPath = getProperty("ObjectPath"); + String adapterObjectPath = getPropertyInternal("ObjectPath"); if (adapterObjectPath == null || objectPath == null) { Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath + " or deviceObjectPath:" + objectPath + " is null"); @@ -1618,7 +1778,7 @@ public class BluetoothService extends IBluetooth.Stub { } /*package*/ String getObjectPathFromAddress(String address) { - String path = getProperty("ObjectPath"); + String path = getPropertyInternal("ObjectPath"); if (path == null) { Log.e(TAG, "Error: Object Path is null"); return null; diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index b29d837..45719e4 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -44,9 +44,16 @@ import android.view.ViewRoot; import android.view.WindowManager; import android.view.WindowManagerImpl; +import java.util.ArrayList; + /** * A wallpaper service is responsible for showing a live wallpaper behind - * applications that would like to sit on top of it. + * applications that would like to sit on top of it. This service object + * itself does very little -- its only purpose is to generate instances of + * {@link Engine} as needed. Implementing a wallpaper thus + * involves subclassing from this, subclassing an Engine implementation, + * and implementing {@link #onCreateEngine()} to return a new instance of + * your engine. */ public abstract class WallpaperService extends Service { /** @@ -78,6 +85,8 @@ public abstract class WallpaperService extends Service { private static final int MSG_TOUCH_EVENT = 10040; private Looper mCallbackLooper; + private final ArrayList<Engine> mActiveEngines + = new ArrayList<Engine>(); static final class WallpaperCommand { String action; @@ -591,8 +600,10 @@ public abstract class WallpaperService extends Service { } void doVisibilityChanged(boolean visible) { - mVisible = visible; - reportVisibility(); + if (!mDestroyed) { + mVisible = visible; + reportVisibility(); + } } void reportVisibility() { @@ -661,6 +672,10 @@ public abstract class WallpaperService extends Service { } void detach() { + if (mDestroyed) { + return; + } + mDestroyed = true; if (mVisible) { @@ -768,10 +783,12 @@ public abstract class WallpaperService extends Service { } Engine engine = onCreateEngine(); mEngine = engine; + mActiveEngines.add(engine); engine.attach(this); return; } case DO_DETACH: { + mActiveEngines.remove(mEngine); mEngine.detach(); return; } @@ -839,6 +856,20 @@ public abstract class WallpaperService extends Service { } } + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + for (int i=0; i<mActiveEngines.size(); i++) { + mActiveEngines.get(i).detach(); + } + mActiveEngines.clear(); + } + /** * Implement to return the implementation of the internal accessibility * service interface. Subclasses should not override. @@ -861,5 +892,11 @@ public abstract class WallpaperService extends Service { mCallbackLooper = looper; } + /** + * Must be implemented to return a new instance of the wallpaper's engine. + * Note that multiple instances may be active at the same time, such as + * when the wallpaper is currently set as the active wallpaper and the user + * is in the wallpaper picker viewing a preview of it as well. + */ public abstract Engine onCreateEngine(); } diff --git a/core/java/android/service/wallpaper/WallpaperSettingsActivity.java b/core/java/android/service/wallpaper/WallpaperSettingsActivity.java index 501947d..aca336f 100644 --- a/core/java/android/service/wallpaper/WallpaperSettingsActivity.java +++ b/core/java/android/service/wallpaper/WallpaperSettingsActivity.java @@ -24,6 +24,7 @@ import android.preference.PreferenceActivity; * Base class for activities that will be used to configure the settings of * a wallpaper. You should derive from this class to allow it to select the * proper theme of the activity depending on how it is being used. + * @hide */ public class WallpaperSettingsActivity extends PreferenceActivity { /** diff --git a/core/java/android/test/TimedTest.java b/core/java/android/test/TimedTest.java deleted file mode 100644 index 3a60a25..0000000 --- a/core/java/android/test/TimedTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.test; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * This annotation can be used on an {@link junit.framework.TestCase}'s test - * methods. When the annotation is present, the test method is timed and the - * results written through instrumentation output. It can also be used on the - * class itself, which is equivalent to tagging all test methods with this - * annotation. - * - * {@hide} Pending approval for public API. - */ -@Retention(RetentionPolicy.RUNTIME) -public @interface TimedTest { }
\ No newline at end of file diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java index 2e76470..b3926f2 100644 --- a/core/java/android/text/method/QwertyKeyListener.java +++ b/core/java/android/text/method/QwertyKeyListener.java @@ -432,7 +432,7 @@ public class QwertyKeyListener extends BaseKeyListener { PICKER_SETS.put('y', "\u00FD\u00FF"); PICKER_SETS.put('z', "\u017A\u017C\u017E"); PICKER_SETS.put(KeyCharacterMap.PICKER_DIALOG_INPUT, - "\u2026\u00A5\u2022\u00AE\u00A9\u00B1[]{}\\"); + "\u2026\u00A5\u2022\u00AE\u00A9\u00B1[]{}\\|"); PICKER_SETS.put('/', "\\"); // From packages/inputmethods/LatinIME/res/xml/kbd_symbols.xml diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index 6995107..aa8d979 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -20,6 +20,7 @@ import android.text.Layout; import android.text.NoCopySpan; import android.text.Layout.Alignment; import android.text.Spannable; +import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.widget.TextView; @@ -156,8 +157,17 @@ public class Touch { padding)); ny = Math.max(ny, 0); + int oldX = widget.getScrollX(); + int oldY = widget.getScrollY(); + scrollTo(widget, layout, nx, ny); - widget.cancelLongPress(); + + // If we actually scrolled, then cancel the up action. + if (oldX != widget.getScrollX() + || oldY != widget.getScrollY()) { + widget.cancelLongPress(); + } + return true; } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 23e7fb7..0ebe360 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -104,8 +104,12 @@ interface IWindowManager int getSwitchStateForDevice(int devid, int sw); int getScancodeState(int sw); int getScancodeStateForDevice(int devid, int sw); + int getTrackballScancodeState(int sw); + int getDPadScancodeState(int sw); int getKeycodeState(int sw); int getKeycodeStateForDevice(int devid, int sw); + int getTrackballKeycodeState(int sw); + int getDPadKeycodeState(int sw); // Report whether the hardware supports the given keys; returns true if successful boolean hasKeys(in int[] keycodes, inout boolean[] keyExists); diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java new file mode 100644 index 0000000..f4215a8 --- /dev/null +++ b/core/java/android/view/ScaleGestureDetector.java @@ -0,0 +1,460 @@ +/* + * 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.view; + +import android.content.Context; +import android.util.DisplayMetrics; + +/** + * Detects transformation gestures involving more than one pointer ("multitouch") + * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener} + * callback will notify users when a particular gesture event has occurred. + * This class should only be used with {@link MotionEvent}s reported via touch. + * + * To use this class: + * <ul> + * <li>Create an instance of the {@code ScaleGestureDetector} for your + * {@link View} + * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call + * {@link #onTouchEvent(MotionEvent)}. The methods defined in your + * callback will be executed when the events occur. + * </ul> + * @hide Pending API approval + */ +public class ScaleGestureDetector { + /** + * The listener for receiving notifications when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnScaleGestureListener}. + * + * An application will receive events in the following order: + * <ul> + * <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} + * <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} + * <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)} + * </ul> + */ + public interface OnScaleGestureListener { + /** + * Responds to scaling events for a gesture in progress. + * Reported by pointer motion. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should consider this event + * as handled. If an event was not handled, the detector + * will continue to accumulate movement until an event is + * handled. This can be useful if an application, for example, + * only wants to update scaling factors if the change is + * greater than 0.01. + */ + public boolean onScale(ScaleGestureDetector detector); + + /** + * Responds to the beginning of a scaling gesture. Reported by + * new pointers going down. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should continue recognizing + * this gesture. For example, if a gesture is beginning + * with a focal point outside of a region where it makes + * sense, onScaleBegin() may return false to ignore the + * rest of the gesture. + */ + public boolean onScaleBegin(ScaleGestureDetector detector); + + /** + * Responds to the end of a scale gesture. Reported by existing + * pointers going up. + * + * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()} + * and {@link ScaleGestureDetector#getFocusY()} will return the location + * of the pointer remaining on the screen. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + */ + public void onScaleEnd(ScaleGestureDetector detector); + } + + /** + * A convenience class to extend when you only want to listen for a subset + * of scaling-related events. This implements all methods in + * {@link OnScaleGestureListener} but does nothing. + * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} and + * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} return + * {@code true}. + */ + public static class SimpleOnScaleGestureListener implements OnScaleGestureListener { + + public boolean onScale(ScaleGestureDetector detector) { + return true; + } + + public boolean onScaleBegin(ScaleGestureDetector detector) { + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) { + // Intentionally empty + } + } + + private static final float PRESSURE_THRESHOLD = 0.67f; + + private Context mContext; + private OnScaleGestureListener mListener; + private boolean mGestureInProgress; + + private MotionEvent mPrevEvent; + private MotionEvent mCurrEvent; + + private float mFocusX; + private float mFocusY; + private float mPrevFingerDiffX; + private float mPrevFingerDiffY; + private float mCurrFingerDiffX; + private float mCurrFingerDiffY; + private float mCurrLen; + private float mPrevLen; + private float mScaleFactor; + private float mCurrPressure; + private float mPrevPressure; + private long mTimeDelta; + + private float mEdgeSlop; + private float mRightSlopEdge; + private float mBottomSlopEdge; + private boolean mSloppyGesture; + + public ScaleGestureDetector(Context context, OnScaleGestureListener listener) { + ViewConfiguration config = ViewConfiguration.get(context); + mContext = context; + mListener = listener; + mEdgeSlop = config.getScaledEdgeSlop(); + } + + public boolean onTouchEvent(MotionEvent event) { + final int action = event.getAction(); + boolean handled = true; + + if (!mGestureInProgress) { + if ((action == MotionEvent.ACTION_POINTER_1_DOWN || + action == MotionEvent.ACTION_POINTER_2_DOWN) && + event.getPointerCount() >= 2) { + // We have a new multi-finger gesture + + // as orientation can change, query the metrics in touch down + DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); + mRightSlopEdge = metrics.widthPixels - mEdgeSlop; + mBottomSlopEdge = metrics.heightPixels - mEdgeSlop; + + // Be paranoid in case we missed an event + reset(); + + mPrevEvent = MotionEvent.obtain(event); + mTimeDelta = 0; + + setContext(event); + + // Check if we have a sloppy gesture. If so, delay + // the beginning of the gesture until we're sure that's + // what the user wanted. Sloppy gestures can happen if the + // edge of the user's hand is touching the screen, for example. + final float edgeSlop = mEdgeSlop; + final float rightSlop = mRightSlopEdge; + final float bottomSlop = mBottomSlopEdge; + final float x0 = event.getRawX(); + final float y0 = event.getRawY(); + final float x1 = getRawX(event, 1); + final float y1 = getRawY(event, 1); + + boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop + || x0 > rightSlop || y0 > bottomSlop; + boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop + || x1 > rightSlop || y1 > bottomSlop; + + if(p0sloppy && p1sloppy) { + mFocusX = -1; + mFocusY = -1; + mSloppyGesture = true; + } else if (p0sloppy) { + mFocusX = event.getX(1); + mFocusY = event.getY(1); + mSloppyGesture = true; + } else if (p1sloppy) { + mFocusX = event.getX(0); + mFocusY = event.getY(0); + mSloppyGesture = true; + } else { + mGestureInProgress = mListener.onScaleBegin(this); + } + } else if (action == MotionEvent.ACTION_MOVE && mSloppyGesture) { + // Initiate sloppy gestures if we've moved outside of the slop area. + final float edgeSlop = mEdgeSlop; + final float rightSlop = mRightSlopEdge; + final float bottomSlop = mBottomSlopEdge; + final float x0 = event.getRawX(); + final float y0 = event.getRawY(); + final float x1 = getRawX(event, 1); + final float y1 = getRawY(event, 1); + + boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop + || x0 > rightSlop || y0 > bottomSlop; + boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop + || x1 > rightSlop || y1 > bottomSlop; + + if(p0sloppy && p1sloppy) { + mFocusX = -1; + mFocusY = -1; + } else if (p0sloppy) { + mFocusX = event.getX(1); + mFocusY = event.getY(1); + } else if (p1sloppy) { + mFocusX = event.getX(0); + mFocusY = event.getY(0); + } else { + mSloppyGesture = false; + mGestureInProgress = mListener.onScaleBegin(this); + } + } else if ((action == MotionEvent.ACTION_POINTER_1_UP + || action == MotionEvent.ACTION_POINTER_2_UP) + && mSloppyGesture) { + // Set focus point to the remaining finger + int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK) + >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0; + mFocusX = event.getX(id); + mFocusY = event.getY(id); + } + } else { + // Transform gesture in progress - attempt to handle it + switch (action) { + case MotionEvent.ACTION_POINTER_1_UP: + case MotionEvent.ACTION_POINTER_2_UP: + // Gesture ended + setContext(event); + + // Set focus point to the remaining finger + int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK) + >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0; + mFocusX = event.getX(id); + mFocusY = event.getY(id); + + if (!mSloppyGesture) { + mListener.onScaleEnd(this); + } + + reset(); + break; + + case MotionEvent.ACTION_CANCEL: + if (!mSloppyGesture) { + mListener.onScaleEnd(this); + } + + reset(); + break; + + case MotionEvent.ACTION_MOVE: + setContext(event); + + // Only accept the event if our relative pressure is within + // a certain limit - this can help filter shaky data as a + // finger is lifted. + if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { + final boolean updatePrevious = mListener.onScale(this); + + if (updatePrevious) { + mPrevEvent.recycle(); + mPrevEvent = MotionEvent.obtain(event); + } + } + break; + } + } + return handled; + } + + /** + * MotionEvent has no getRawX(int) method; simulate it pending future API approval. + */ + private static float getRawX(MotionEvent event, int pointerIndex) { + float offset = event.getX() - event.getRawX(); + return event.getX(pointerIndex) + offset; + } + + /** + * MotionEvent has no getRawY(int) method; simulate it pending future API approval. + */ + private static float getRawY(MotionEvent event, int pointerIndex) { + float offset = event.getY() - event.getRawY(); + return event.getY(pointerIndex) + offset; + } + + private void setContext(MotionEvent curr) { + if (mCurrEvent != null) { + mCurrEvent.recycle(); + } + mCurrEvent = MotionEvent.obtain(curr); + + mCurrLen = -1; + mPrevLen = -1; + mScaleFactor = -1; + + final MotionEvent prev = mPrevEvent; + + final float px0 = prev.getX(0); + final float py0 = prev.getY(0); + final float px1 = prev.getX(1); + final float py1 = prev.getY(1); + final float cx0 = curr.getX(0); + final float cy0 = curr.getY(0); + final float cx1 = curr.getX(1); + final float cy1 = curr.getY(1); + + final float pvx = px1 - px0; + final float pvy = py1 - py0; + final float cvx = cx1 - cx0; + final float cvy = cy1 - cy0; + mPrevFingerDiffX = pvx; + mPrevFingerDiffY = pvy; + mCurrFingerDiffX = cvx; + mCurrFingerDiffY = cvy; + + mFocusX = cx0 + cvx * 0.5f; + mFocusY = cy0 + cvy * 0.5f; + mTimeDelta = curr.getEventTime() - prev.getEventTime(); + mCurrPressure = curr.getPressure(0) + curr.getPressure(1); + mPrevPressure = prev.getPressure(0) + prev.getPressure(1); + } + + private void reset() { + if (mPrevEvent != null) { + mPrevEvent.recycle(); + mPrevEvent = null; + } + if (mCurrEvent != null) { + mCurrEvent.recycle(); + mCurrEvent = null; + } + mSloppyGesture = false; + mGestureInProgress = false; + } + + /** + * Returns {@code true} if a two-finger scale gesture is in progress. + * @return {@code true} if a scale gesture is in progress, {@code false} otherwise. + */ + public boolean isInProgress() { + return mGestureInProgress; + } + + /** + * Get the X coordinate of the current gesture's focal point. + * If a gesture is in progress, the focal point is directly between + * the two pointers forming the gesture. + * If a gesture is ending, the focal point is the location of the + * remaining pointer on the screen. + * If {@link #isInProgress()} would return false, the result of this + * function is undefined. + * + * @return X coordinate of the focal point in pixels. + */ + public float getFocusX() { + return mFocusX; + } + + /** + * Get the Y coordinate of the current gesture's focal point. + * If a gesture is in progress, the focal point is directly between + * the two pointers forming the gesture. + * If a gesture is ending, the focal point is the location of the + * remaining pointer on the screen. + * If {@link #isInProgress()} would return false, the result of this + * function is undefined. + * + * @return Y coordinate of the focal point in pixels. + */ + public float getFocusY() { + return mFocusY; + } + + /** + * Return the current distance between the two pointers forming the + * gesture in progress. + * + * @return Distance between pointers in pixels. + */ + public float getCurrentSpan() { + if (mCurrLen == -1) { + final float cvx = mCurrFingerDiffX; + final float cvy = mCurrFingerDiffY; + mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy); + } + return mCurrLen; + } + + /** + * Return the previous distance between the two pointers forming the + * gesture in progress. + * + * @return Previous distance between pointers in pixels. + */ + public float getPreviousSpan() { + if (mPrevLen == -1) { + final float pvx = mPrevFingerDiffX; + final float pvy = mPrevFingerDiffY; + mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy); + } + return mPrevLen; + } + + /** + * Return the scaling factor from the previous scale event to the current + * event. This value is defined as + * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}). + * + * @return The current scaling factor. + */ + public float getScaleFactor() { + if (mScaleFactor == -1) { + mScaleFactor = getCurrentSpan() / getPreviousSpan(); + } + return mScaleFactor; + } + + /** + * Return the time difference in milliseconds between the previous + * accepted scaling event and the current scaling event. + * + * @return Time difference since the last scaling event in milliseconds. + */ + public long getTimeDelta() { + return mTimeDelta; + } + + /** + * Return the event time of the current event being processed. + * + * @return Current event time in milliseconds. + */ + public long getEventTime() { + return mCurrEvent.getEventTime(); + } +} diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index fd173eb..9a8ee02 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -55,15 +55,14 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { } }, 2)); - final float mPastX[] = new float[NUM_PAST]; - final float mPastY[] = new float[NUM_PAST]; - final long mPastTime[] = new long[NUM_PAST]; + final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; + final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; + final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; + float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; + float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; int mLastTouch; - float mYVelocity; - float mXVelocity; - private VelocityTracker mNext; /** @@ -107,9 +106,11 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * Reset the velocity tracker back to its initial state. */ public void clear() { - final long[] pastTime = mPastTime; - for (int i = 0; i < NUM_PAST; i++) { - pastTime[i] = 0; + final long[][] pastTime = mPastTime; + for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) { + for (int i = 0; i < NUM_PAST; i++) { + pastTime[p][i] = 0; + } } } @@ -125,18 +126,21 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { public void addMovement(MotionEvent ev) { long time = ev.getEventTime(); final int N = ev.getHistorySize(); - for (int i=0; i<N; i++) { - addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), - ev.getHistoricalEventTime(i)); + final int pointerCount = ev.getPointerCount(); + for (int p = 0; p < pointerCount; p++) { + for (int i=0; i<N; i++) { + addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i), + ev.getHistoricalEventTime(i)); + } + addPoint(p, ev.getX(p), ev.getY(p), time); } - addPoint(ev.getX(), ev.getY(), time); } - private void addPoint(float x, float y, long time) { + private void addPoint(int pos, float x, float y, long time) { final int lastTouch = (mLastTouch + 1) % NUM_PAST; - mPastX[lastTouch] = x; - mPastY[lastTouch] = y; - mPastTime[lastTouch] = time; + mPastX[pos][lastTouch] = x; + mPastY[pos][lastTouch] = y; + mPastTime[pos][lastTouch] = time; mLastTouch = lastTouch; } @@ -164,48 +168,53 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * must be positive. */ public void computeCurrentVelocity(int units, float maxVelocity) { - final float[] pastX = mPastX; - final float[] pastY = mPastY; - final long[] pastTime = mPastTime; - final int lastTouch = mLastTouch; + for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) { + final float[] pastX = mPastX[pos]; + final float[] pastY = mPastY[pos]; + final long[] pastTime = mPastTime[pos]; + final int lastTouch = mLastTouch; - // find oldest acceptable time - int oldestTouch = lastTouch; - if (pastTime[lastTouch] > 0) { // cleared ? - oldestTouch = (lastTouch + 1) % NUM_PAST; - final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME; - while (pastTime[oldestTouch] < acceptableTime) { - oldestTouch = (oldestTouch + 1) % NUM_PAST; + // find oldest acceptable time + int oldestTouch = lastTouch; + if (pastTime[lastTouch] > 0) { // cleared ? + oldestTouch = (lastTouch + 1) % NUM_PAST; + final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME; + while (pastTime[oldestTouch] < acceptableTime) { + oldestTouch = (oldestTouch + 1) % NUM_PAST; + } } - } - // Kind-of stupid. - final float oldestX = pastX[oldestTouch]; - final float oldestY = pastY[oldestTouch]; - final long oldestTime = pastTime[oldestTouch]; - float accumX = 0; - float accumY = 0; - float N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; - // Skip the last received event, since it is probably pretty noisy. - if (N > 3) N--; - - for (int i=1; i < N; i++) { - final int j = (oldestTouch + i) % NUM_PAST; - final int dur = (int)(pastTime[j] - oldestTime); - if (dur == 0) continue; - float dist = pastX[j] - oldestX; - float vel = (dist/dur) * units; // pixels/frame. - accumX = (accumX == 0) ? vel : (accumX + vel) * .5f; + // Kind-of stupid. + final float oldestX = pastX[oldestTouch]; + final float oldestY = pastY[oldestTouch]; + final long oldestTime = pastTime[oldestTouch]; + float accumX = 0; + float accumY = 0; + float N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; + // Skip the last received event, since it is probably pretty noisy. + if (N > 3) N--; + + for (int i=1; i < N; i++) { + final int j = (oldestTouch + i) % NUM_PAST; + final int dur = (int)(pastTime[j] - oldestTime); + if (dur == 0) continue; + float dist = pastX[j] - oldestX; + float vel = (dist/dur) * units; // pixels/frame. + accumX = (accumX == 0) ? vel : (accumX + vel) * .5f; + + dist = pastY[j] - oldestY; + vel = (dist/dur) * units; // pixels/frame. + accumY = (accumY == 0) ? vel : (accumY + vel) * .5f; + } - dist = pastY[j] - oldestY; - vel = (dist/dur) * units; // pixels/frame. - accumY = (accumY == 0) ? vel : (accumY + vel) * .5f; + mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity) + : Math.min(accumX, maxVelocity); + mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity) + : Math.min(accumY, maxVelocity); + + if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" + + mXVelocity + " N=" + N); } - mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity); - mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity); - - if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" - + mXVelocity + " N=" + N); } /** @@ -215,7 +224,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed X velocity. */ public float getXVelocity() { - return mXVelocity; + return mXVelocity[0]; } /** @@ -225,6 +234,32 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed Y velocity. */ public float getYVelocity() { - return mYVelocity; + return mYVelocity[0]; + } + + /** + * Retrieve the last computed X velocity. You must first call + * {@link #computeCurrentVelocity(int)} before calling this function. + * + * @param pos Which pointer's velocity to return. + * @return The previously computed X velocity. + * + * @hide Pending API approval + */ + public float getXVelocity(int pos) { + return mXVelocity[pos]; + } + + /** + * Retrieve the last computed Y velocity. You must first call + * {@link #computeCurrentVelocity(int)} before calling this function. + * + * @param pos Which pointer's velocity to return. + * @return The previously computed Y velocity. + * + * @hide Pending API approval + */ + public float getYVelocity(int pos) { + return mYVelocity[pos]; } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0b87536..1fc3678 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4324,8 +4324,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility requestLayout(); invalidate(); - if (((mViewFlags & VISIBILITY_MASK) == GONE) && hasFocus()) { - clearFocus(); + if (((mViewFlags & VISIBILITY_MASK) == GONE)) { + if (hasFocus()) clearFocus(); + destroyDrawingCache(); } if (mAttachInfo != null) { mAttachInfo.mViewVisibilityChanged = true; @@ -6283,6 +6284,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= DRAWN; + mPrivateFlags |= DRAWING_CACHE_VALID; // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { @@ -6301,7 +6303,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility // Restore the cached Canvas for our siblings attachInfo.mCanvas = canvas; } - mPrivateFlags |= DRAWING_CACHE_VALID; } } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index bef3e58..4e12250 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -1407,8 +1407,8 @@ public final class ViewRoot extends Handler implements ViewParent, // When in touch mode, focus points to the previously focused view, // which may have been removed from the view hierarchy. The following - // line checks whether the view is still in the hierarchy - if (focus == null || focus.getParent() == null) { + // line checks whether the view is still in our hierarchy. + if (focus == null || focus.mAttachInfo != mAttachInfo) { mRealFocusedView = null; return false; } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 1ab46fc..083793b 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -380,6 +380,8 @@ public interface WindowManagerPolicy { public final int OFF_BECAUSE_OF_USER = 1; /** Screen turned off because of timeout */ public final int OFF_BECAUSE_OF_TIMEOUT = 2; + /** Screen turned off because of proximity sensor */ + public final int OFF_BECAUSE_OF_PROX_SENSOR = 3; /** * Magic constant to {@link IWindowManager#setRotation} to not actually diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index 75028de..c74092e 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -74,6 +74,11 @@ public final class CacheManager { // Flag to clear the cache when the CacheManager is initialized private static boolean mClearCacheOnInit = false; + /** + * This class represents a resource retrieved from the HTTP cache. + * Instances of this class can be obtained by invoking the + * CacheManager.getCacheFile() method. + */ public static class CacheResult { // these fields are saved to the database int httpStatusCode; diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index f760b61..e9afcb6 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -652,7 +652,7 @@ class CallbackProxy extends Handler { String message = msg.getData().getString("message"); String sourceID = msg.getData().getString("sourceID"); int lineNumber = msg.getData().getInt("lineNumber"); - mWebChromeClient.addMessageToConsole(message, lineNumber, sourceID); + mWebChromeClient.onConsoleMessage(message, lineNumber, sourceID); break; case GET_VISITED_HISTORY: 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/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java index 64a9d9b..d12d828 100755 --- a/core/java/android/webkit/GeolocationPermissions.java +++ b/core/java/android/webkit/GeolocationPermissions.java @@ -26,8 +26,22 @@ import java.util.Set; /** - * Implements the Java side of GeolocationPermissions. Simply marshalls calls - * from the UI thread to the WebKit thread. + * This class is used to get Geolocation permissions from, and set them on the + * WebView. For example, it could be used to allow a user to manage Geolocation + * permissions from a browser's UI. + * + * Permissions are managed on a per-origin basis, as required by the + * Geolocation spec - http://dev.w3.org/geo/api/spec-source.html. An origin + * specifies the scheme, host and port of particular frame. An origin is + * represented here as a string, using the output of + * WebCore::SecurityOrigin::toString. + * + * This class is the Java counterpart of the WebKit C++ GeolocationPermissions + * class. It simply marshalls calls from the UI thread to the WebKit thread. + * + * Within WebKit, Geolocation permissions may be applied either temporarily + * (for the duration of the page) or permanently. This class deals only with + * permanent permissions. */ public final class GeolocationPermissions { /** @@ -92,8 +106,8 @@ public final class GeolocationPermissions { switch (msg.what) { case RETURN_ORIGINS: { Map values = (Map) msg.obj; - Set origins = (Set) values.get(ORIGINS); - ValueCallback<Set> callback = (ValueCallback<Set>) values.get(CALLBACK); + Set<String> origins = (Set<String>) values.get(ORIGINS); + ValueCallback<Set<String> > callback = (ValueCallback<Set<String> >) values.get(CALLBACK); callback.onReceiveValue(origins); } break; case RETURN_ALLOWED: { @@ -122,10 +136,9 @@ public final class GeolocationPermissions { case GET_ORIGINS: { getOriginsImpl(); ValueCallback callback = (ValueCallback) msg.obj; - Set origins = new HashSet(mOrigins); Map values = new HashMap<String, Object>(); values.put(CALLBACK, callback); - values.put(ORIGINS, origins); + values.put(ORIGINS, mOrigins); postUIMessage(Message.obtain(null, RETURN_ORIGINS, values)); } break; case GET_ALLOWED: { @@ -185,15 +198,17 @@ public final class GeolocationPermissions { * Gets the set of origins for which Geolocation permissions are stored. * Note that we represent the origins as strings. These are created using * WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules' - * (Database, Geolocation etc) do so, it's safe to match up origins for the - * purposes of displaying UI. + * (Database, Geolocation etc) do so, it's safe to match up origins based + * on this string. + * + * Callback is a ValueCallback object whose onReceiveValue method will be + * called asynchronously with the set of origins. */ - public void getOrigins(ValueCallback<Set> callback) { + public void getOrigins(ValueCallback<Set<String> > callback) { if (callback != null) { if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) { getOriginsImpl(); - Set origins = new HashSet(mOrigins); - callback.onReceiveValue(origins); + callback.onReceiveValue(mOrigins); } else { postMessage(Message.obtain(null, GET_ORIGINS, callback)); } @@ -210,6 +225,9 @@ public final class GeolocationPermissions { /** * Gets the permission state for the specified origin. + * + * Callback is a ValueCallback object whose onReceiveValue method will be + * called asynchronously with the permission state for the origin. */ public void getAllowed(String origin, ValueCallback<Boolean> callback) { if (callback == null) { @@ -231,7 +249,7 @@ public final class GeolocationPermissions { } /** - * Helper method to get the permission state. + * Helper method to get the permission state for the specified origin. */ private void getAllowedImpl(String origin) { // Called on the WebKit thread. diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 92676aa..8ca4142 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -256,29 +256,34 @@ public class WebChromeClient { } /** - * Add a JavaScript error message to the console. Clients should override - * this to process the log message as they see fit. + * Report a JavaScript error message to the host application. The ChromeClient + * should override this to process the log message as they see fit. * @param message The error message to report. * @param lineNumber The line number of the error. * @param sourceID The name of the source file that caused the error. */ - public void addMessageToConsole(String message, int lineNumber, String sourceID) {} + public void onConsoleMessage(String message, int lineNumber, String sourceID) {} /** - * Ask the host application for an icon to represent a <video> element. - * This icon will be used if the Web page did not specify a poster attribute. + * When not playing, video elements are represented by a 'poster' image. The + * image to use can be specified by the poster attribute of the video tag in + * HTML. If the attribute is absent, then a default poster will be used. This + * method allows the ChromeClient to provide that default image. * - * @return Bitmap The icon or null if no such icon is available. + * @return Bitmap The image to use as a default poster, or null if no such image is + * available. */ public Bitmap getDefaultVideoPoster() { return null; } /** - * Ask the host application for a custom progress view to show while - * a <video> is loading. + * When the user starts to playback a video element, it may take time for enough + * data to be buffered before the first frames can be rendered. While this buffering + * is taking place, the ChromeClient can use this function to provide a View to be + * displayed. For example, the ChromeClient could show a spinner animation. * - * @return View The progress view. + * @return View The View to be displayed whilst the video is loading. */ public View getVideoLoadingProgressView() { return null; diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 6f3262a..093756d 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -411,6 +411,7 @@ public class WebSettings { */ public void setSupportZoom(boolean support) { mSupportZoom = support; + mWebView.updateMultiTouchSupport(mContext); } /** @@ -425,6 +426,7 @@ public class WebSettings { */ public void setBuiltInZoomControls(boolean enabled) { mBuiltInZoomControls = enabled; + mWebView.updateMultiTouchSupport(mContext); } /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 142dffb..c5c14d3 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnCancelListener; +import android.content.pm.PackageManager; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -49,6 +50,7 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.ScaleGestureDetector; import android.view.SoundEffectConstants; import android.view.VelocityTracker; import android.view.View; @@ -198,6 +200,8 @@ public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewGroup.OnHierarchyChangeListener { + // enable debug output for drag trackers + private static final boolean DEBUG_DRAG_TRACKER = false; // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing // the screen all-the-time. Good for profiling our drawing code static private final boolean AUTO_REDRAW_HACK = false; @@ -349,6 +353,7 @@ public class WebView extends AbsoluteLayout private static final int TOUCH_DOUBLE_TAP_MODE = 6; private static final int TOUCH_DONE_MODE = 7; private static final int TOUCH_SELECT_MODE = 8; + private static final int TOUCH_PINCH_DRAG = 9; // Whether to forward the touch events to WebCore private boolean mForwardTouchEvents = false; @@ -428,6 +433,18 @@ public class WebView extends AbsoluteLayout private boolean mWrapContent; + // whether support multi-touch + private boolean mSupportMultiTouch; + // use the framework's ScaleGestureDetector to handle multi-touch + private ScaleGestureDetector mScaleDetector; + // minimum scale change during multi-touch zoom + private static float PREVIEW_SCALE_INCREMENT = 0.01f; + + // the anchor point in the document space where VIEW_SIZE_CHANGED should + // apply to + private int mAnchorX; + private int mAnchorY; + /** * Private message ids */ @@ -450,6 +467,7 @@ public class WebView extends AbsoluteLayout static final int UPDATE_TEXT_ENTRY_MSG_ID = 15; static final int WEBCORE_INITIALIZED_MSG_ID = 16; static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; + static final int UPDATE_ZOOM_RANGE = 18; static final int MOVE_OUT_OF_PLUGIN = 19; static final int CLEAR_TEXT_ENTRY = 20; static final int UPDATE_TEXT_SELECTION_MSG_ID = 21; @@ -460,6 +478,7 @@ public class WebView extends AbsoluteLayout // obj=Rect in doc coordinates static final int INVAL_RECT_MSG_ID = 26; static final int REQUEST_KEYBOARD = 27; + static final int SHOW_RECT_MSG_ID = 28; static final String[] HandlerDebugString = { "REMEMBER_PASSWORD", // = 1; @@ -479,7 +498,7 @@ public class WebView extends AbsoluteLayout "UPDATE_TEXT_ENTRY_MSG_ID", // = 15; "WEBCORE_INITIALIZED_MSG_ID", // = 16; "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17; - "18", // = 18; + "UPDATE_ZOOM_RANGE", // = 18; "MOVE_OUT_OF_PLUGIN", // = 19; "CLEAR_TEXT_ENTRY", // = 20; "UPDATE_TEXT_SELECTION_MSG_ID", // = 21; @@ -488,7 +507,8 @@ public class WebView extends AbsoluteLayout "PREVENT_TOUCH_ID", // = 24; "WEBCORE_NEED_TOUCH_EVENTS", // = 25; "INVAL_RECT_MSG_ID", // = 26; - "REQUEST_KEYBOARD" // = 27; + "REQUEST_KEYBOARD", // = 27; + "SHOW_RECT_MSG_ID" // = 28; }; // default scale limit. Depending on the display density @@ -500,7 +520,7 @@ public class WebView extends AbsoluteLayout private boolean mMinZoomScaleFixed = true; // initial scale in percent. 0 means using default. - private int mInitialScale = 0; + private int mInitialScaleInPercent = 0; // while in the zoom overview mode, the page's width is fully fit to the // current window. The page is alive, in another words, you can click to @@ -511,7 +531,7 @@ public class WebView extends AbsoluteLayout // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn, // engadget always have wider mContentWidth no matter what viewport size is. int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH; - float mLastScale; + float mTextWrapScale; // default scale. Depending on the display density. static int DEFAULT_SCALE_PERCENT; @@ -743,6 +763,20 @@ public class WebView extends AbsoluteLayout params; frameParams.gravity = Gravity.RIGHT; } + updateMultiTouchSupport(context); + } + + void updateMultiTouchSupport(Context context) { + WebSettings settings = getSettings(); + mSupportMultiTouch = context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) + && settings.supportZoom() && settings.getBuiltInZoomControls(); + if (mSupportMultiTouch && (mScaleDetector == null)) { + mScaleDetector = new ScaleGestureDetector(context, + new ScaleDetectorListener()); + } else if (!mSupportMultiTouch && (mScaleDetector != null)) { + mScaleDetector = null; + } } private void updateZoomButtonsEnabled() { @@ -785,6 +819,7 @@ public class WebView extends AbsoluteLayout mDefaultScale = density; mActualScale = density; mInvActualScale = 1 / density; + mTextWrapScale = density; DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; DEFAULT_MIN_ZOOM_SCALE = 0.25f * density; mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; @@ -805,7 +840,7 @@ public class WebView extends AbsoluteLayout mDefaultScale = density; mMaxZoomScale *= scaleFactor; mMinZoomScale *= scaleFactor; - setNewZoomScale(mActualScale * scaleFactor, false); + setNewZoomScale(mActualScale * scaleFactor, true, false); } } @@ -1162,9 +1197,8 @@ public class WebView extends AbsoluteLayout b.putInt("scrollX", mScrollX); b.putInt("scrollY", mScrollY); b.putFloat("scale", mActualScale); - if (mInZoomOverview) { - b.putFloat("lastScale", mLastScale); - } + b.putFloat("textwrapScale", mTextWrapScale); + b.putBoolean("overview", mInZoomOverview); return true; } return false; @@ -1209,13 +1243,8 @@ public class WebView extends AbsoluteLayout // onSizeChanged() is called, the rest will be set // correctly mActualScale = scale; - float lastScale = b.getFloat("lastScale", -1.0f); - if (lastScale > 0) { - mInZoomOverview = true; - mLastScale = lastScale; - } else { - mInZoomOverview = false; - } + mTextWrapScale = b.getFloat("textwrapScale", scale); + mInZoomOverview = b.getBoolean("overview"); invalidate(); return true; } @@ -1525,7 +1554,7 @@ public class WebView extends AbsoluteLayout } nativeClearCursor(); // start next trackball movement from page edge if (bottom) { - return pinScrollTo(mScrollX, mContentHeight, true, 0); + return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0); } // Page down. int h = getHeight(); @@ -1595,7 +1624,7 @@ public class WebView extends AbsoluteLayout * @param scaleInPercent The initial scale in percent. */ public void setInitialScale(int scaleInPercent) { - mInitialScale = scaleInPercent; + mInitialScaleInPercent = scaleInPercent; } /** @@ -1938,12 +1967,18 @@ public class WebView extends AbsoluteLayout contentSizeChanged(updateLayout); } - private void setNewZoomScale(float scale, boolean force) { + private void setNewZoomScale(float scale, boolean updateTextWrapScale, + boolean force) { if (scale < mMinZoomScale) { scale = mMinZoomScale; } else if (scale > mMaxZoomScale) { scale = mMaxZoomScale; } + if (updateTextWrapScale) { + mTextWrapScale = scale; + // reset mLastHeightSent to force VIEW_SIZE_CHANGED sent to WebKit + mLastHeightSent = 0; + } if (scale != mActualScale || force) { if (mDrawHistory) { // If history Picture is drawn, don't update scroll. They will @@ -1953,9 +1988,7 @@ public class WebView extends AbsoluteLayout } mActualScale = scale; mInvActualScale = 1 / scale; - if (!mPreviewZoomOnly) { - sendViewSizeZoom(); - } + sendViewSizeZoom(); } else { // update our scroll so we don't appear to jump // i.e. keep the center of the doc in the center of the view @@ -1983,10 +2016,9 @@ public class WebView extends AbsoluteLayout mScrollX = pinLocX(Math.round(sx)); mScrollY = pinLocY(Math.round(sy)); - if (!mPreviewZoomOnly) { - sendViewSizeZoom(); - sendOurVisibleRect(); - } + // update webkit + sendViewSizeZoom(); + sendOurVisibleRect(); } } } @@ -1996,6 +2028,8 @@ public class WebView extends AbsoluteLayout private Rect mLastGlobalRect; private Rect sendOurVisibleRect() { + if (mPreviewZoomOnly) return mLastVisibleRectSent; + Rect rect = new Rect(); calcOurContentVisibleRect(rect); // Rect.equals() checks for null input. @@ -2049,6 +2083,8 @@ public class WebView extends AbsoluteLayout int mWidth; int mHeight; int mTextWrapWidth; + int mAnchorX; + int mAnchorY; float mScale; boolean mIgnoreHeight; } @@ -2060,6 +2096,8 @@ public class WebView extends AbsoluteLayout * @return true if new values were sent */ private boolean sendViewSizeZoom() { + if (mPreviewZoomOnly) return false; + int viewWidth = getViewWidth(); int newWidth = Math.round(viewWidth * mInvActualScale); int newHeight = Math.round(getViewHeight() * mInvActualScale); @@ -2079,16 +2117,15 @@ public class WebView extends AbsoluteLayout ViewSizeData data = new ViewSizeData(); data.mWidth = newWidth; data.mHeight = newHeight; - // while in zoom overview mode, the text are wrapped to the screen - // width matching mLastScale. So that we don't trigger re-flow while - // toggling between overview mode and normal mode. - data.mTextWrapWidth = mInZoomOverview ? Math.round(viewWidth - / mLastScale) : newWidth; + data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);; data.mScale = mActualScale; data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure; + data.mAnchorX = mAnchorX; + data.mAnchorY = mAnchorY; mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data); mLastWidthSent = newWidth; mLastHeightSent = newHeight; + mAnchorX = mAnchorY = 0; return true; } return false; @@ -2730,7 +2767,7 @@ public class WebView extends AbsoluteLayout */ @Deprecated public static synchronized PluginList getPluginList() { - return null; + return new PluginList(); } /** @@ -2762,16 +2799,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, @@ -2784,6 +2812,21 @@ public class WebView extends AbsoluteLayout 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. @@ -2838,6 +2881,38 @@ public class WebView extends AbsoluteLayout */ private boolean mNeedToAdjustWebTextView; + // if checkVisibility is false, the WebTextView may trigger a move of + // WebView to bring itself into the view. + private void adjustTextView(boolean checkVisibility) { + Rect contentBounds = nativeFocusCandidateNodeBounds(); + Rect vBox = contentToViewRect(contentBounds); + Rect visibleRect = new Rect(); + calcOurVisibleRect(visibleRect); + if (!checkVisibility || visibleRect.contains(vBox)) { + // As a result of the zoom, the textfield is now on + // screen. Place the WebTextView in its new place, + // accounting for our new scroll/zoom values. + mWebTextView + .setTextSize( + TypedValue.COMPLEX_UNIT_PX, + contentToViewDimension(nativeFocusCandidateTextSize())); + mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), + vBox.height()); + // If it is a password field, start drawing the + // WebTextView once again. + if (nativeFocusCandidateIsPassword()) { + mWebTextView.setInPassword(true); + } + } else { + // The textfield is now off screen. The user probably + // was not zooming to see the textfield better. Remove + // the WebTextView. If the user types a key, and the + // textfield is still in focus, we will reconstruct + // the WebTextView and scroll it back on screen. + mWebTextView.remove(); + } + } + private void drawCoreAndCursorRing(Canvas canvas, int color, boolean drawCursorRing) { if (mDrawHistory) { @@ -2865,32 +2940,7 @@ public class WebView extends AbsoluteLayout invalidate(); if (mNeedToAdjustWebTextView) { mNeedToAdjustWebTextView = false; - Rect contentBounds = nativeFocusCandidateNodeBounds(); - Rect vBox = contentToViewRect(contentBounds); - Rect visibleRect = new Rect(); - calcOurVisibleRect(visibleRect); - if (visibleRect.contains(vBox)) { - // As a result of the zoom, the textfield is now on - // screen. Place the WebTextView in its new place, - // accounting for our new scroll/zoom values. - mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, - contentToViewDimension( - nativeFocusCandidateTextSize())); - mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), - vBox.height()); - // If it is a password field, start drawing the - // WebTextView once again. - if (nativeFocusCandidateIsPassword()) { - mWebTextView.setInPassword(true); - } - } else { - // The textfield is now off screen. The user probably - // was not zooming to see the textfield better. Remove - // the WebTextView. If the user types a key, and the - // textfield is still in focus, we will reconstruct - // the WebTextView and scroll it back on screen. - mWebTextView.remove(); - } + adjustTextView(true); } } // calculate the intermediate scroll position. As we need to use @@ -2924,11 +2974,11 @@ public class WebView extends AbsoluteLayout canvas.scale(mActualScale, mActualScale); } - mWebViewCore.drawContentPicture(canvas, color, animateZoom, - animateScroll); + mWebViewCore.drawContentPicture(canvas, color, + (animateZoom || mPreviewZoomOnly), animateScroll); if (mNativeClass == 0) return; - if (mShiftIsPressed && !animateZoom) { + if (mShiftIsPressed && !(animateZoom || mPreviewZoomOnly)) { if (mTouchSelection) { nativeDrawSelectionRegion(canvas); } else { @@ -3029,10 +3079,15 @@ public class WebView extends AbsoluteLayout if (mWebTextView == null) return; imm.showSoftInput(mWebTextView, 0); - if (mInZoomOverview) { - // if in zoom overview mode, call doDoubleTap() to bring it back - // to normal mode so that user can enter text. - doDoubleTap(); + if (mActualScale < mDefaultScale) { + // bring it back to the default scale so that user can enter + // text. + mInZoomOverview = false; + mZoomCenterX = mLastTouchX; + mZoomCenterY = mLastTouchY; + // do not change text wrap scale so that there is no reflow + setNewZoomScale(mDefaultScale, false, false); + adjustTextView(false); } } else { // used by plugins @@ -3604,6 +3659,8 @@ public class WebView extends AbsoluteLayout if (mZoomScale == 0) { // unless we're already zooming mZoomCenterX = getViewWidth() * .5f; mZoomCenterY = getViewHeight() * .5f; + mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); + mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); } // update mMinZoomScale if the minimum zoom scale is not fixed @@ -3615,11 +3672,19 @@ public class WebView extends AbsoluteLayout mMinZoomScale = Math.min(1.0f, (float) getViewWidth() / (mDrawHistory ? mHistoryPicture.getWidth() : mZoomOverviewWidth)); + if (mInitialScaleInPercent > 0) { + // limit the minZoomScale to the initialScale if it is set + float initialScale = mInitialScaleInPercent / 100.0f; + if (mMinZoomScale > initialScale) { + mMinZoomScale = initialScale; + } + } } // we always force, in case our height changed, in which case we still // want to send the notification over to webkit - setNewZoomScale(mActualScale, true); + // only update the text wrap scale if width changed. + setNewZoomScale(mActualScale, w != ow, true); } @Override @@ -3667,6 +3732,229 @@ 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 class ScaleDetectorListener implements + ScaleGestureDetector.OnScaleGestureListener { + + public boolean onScaleBegin(ScaleGestureDetector detector) { + // cancel the single touch handling + cancelTouch(); + if (mZoomButtonsController.isVisible()) { + mZoomButtonsController.setVisible(false); + } + // reset the zoom overview mode so that the page won't auto grow + mInZoomOverview = false; + // If it is in password mode, turn it off so it does not draw + // misplaced. + if (inEditingMode() && nativeFocusCandidateIsPassword()) { + mWebTextView.setInPassword(false); + } + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) { + if (mPreviewZoomOnly) { + mPreviewZoomOnly = false; + mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); + mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); + // don't reflow when zoom in; when zoom out, do reflow if the + // new scale is almost minimum scale; + boolean reflowNow = (mActualScale - mMinZoomScale <= 0.01f) + || ((mActualScale <= 0.8 * mTextWrapScale)); + // force zoom after mPreviewZoomOnly is set to false so that the + // new view size will be passed to the WebKit + setNewZoomScale(mActualScale, reflowNow, true); + // call invalidate() to draw without zoom filter + invalidate(); + } + // adjust the edit text view if needed + if (inEditingMode()) { + adjustTextView(true); + } + // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it + // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it + // may trigger the unwanted fling. + mTouchMode = TOUCH_PINCH_DRAG; + startTouch(detector.getFocusX(), detector.getFocusY(), + mLastTouchTime); + } + + public boolean onScale(ScaleGestureDetector detector) { + float scale = (float) (Math.round(detector.getScaleFactor() + * mActualScale * 100) / 100.0); + if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) { + mPreviewZoomOnly = true; + // limit the scale change per step + if (scale > mActualScale) { + scale = Math.min(scale, mActualScale * 1.25f); + } else { + scale = Math.max(scale, mActualScale * 0.8f); + } + mZoomCenterX = detector.getFocusX(); + mZoomCenterY = detector.getFocusY(); + setNewZoomScale(scale, false, false); + invalidate(); + return true; + } + return false; + } + } + + // 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 || DEBUG_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 || DEBUG_DRAG_TRACKER) { + Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "---- stretch " + sx + + " " + sy); + } + if (mProxy.onStretchChange(sx, sy)) { + invalidate(); + } + } + } + + public void stopDrag() { + if (DebugFlags.DRAG_TRACKER || DEBUG_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 || DEBUG_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 || DEBUG_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()) { @@ -3678,11 +3966,41 @@ public class WebView extends AbsoluteLayout + mTouchMode); } - int action = ev.getAction(); - float x = ev.getX(); - float y = ev.getY(); + int action; + float x, y; long eventTime = ev.getEventTime(); + // FIXME: we may consider to give WebKit an option to handle multi-touch + // events later. + if (mSupportMultiTouch && mMinZoomScale < mMaxZoomScale + && ev.getPointerCount() > 1) { + mScaleDetector.onTouchEvent(ev); + if (mScaleDetector.isInProgress()) { + mLastTouchTime = eventTime; + return true; + } + x = mScaleDetector.getFocusX(); + y = mScaleDetector.getFocusY(); + action = ev.getAction() & MotionEvent.ACTION_MASK; + if (action == MotionEvent.ACTION_POINTER_DOWN) { + cancelTouch(); + action = MotionEvent.ACTION_DOWN; + } else if (action == MotionEvent.ACTION_POINTER_UP) { + // set mLastTouchX/Y to the remaining point + mLastTouchX = x; + mLastTouchY = y; + } else if (action == MotionEvent.ACTION_MOVE) { + // negative x or y indicate it is on the edge, skip it. + if (x < 0 || y < 0) { + return true; + } + } + } else { + action = ev.getAction(); + x = ev.getX(); + y = ev.getY(); + } + // Due to the touch screen edge effect, a touch closer to the edge // always snapped to the edge. As getViewWidth() can be different from // getWidth() due to the scrollbar, adjusting the point to match @@ -3738,6 +4056,7 @@ public class WebView extends AbsoluteLayout // continue, mTouchMode should be still TOUCH_INIT_MODE } } else { + mPreviewZoomOnly = false; mTouchMode = TOUCH_INIT_MODE; mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES : PREVENT_DRAG_NO; @@ -3755,11 +4074,11 @@ public class WebView extends AbsoluteLayout .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); } // Remember where the motion event started - mLastTouchX = x; - mLastTouchY = y; - mLastTouchTime = eventTime; - mVelocityTracker = VelocityTracker.obtain(); - mSnapScrollMode = SNAP_NONE; + startTouch(x, y, eventTime); + if (mDragTracker != null) { + mDragTrackerHandler = new DragTrackerHandler(x, y, + mDragTracker); + } break; } case MotionEvent.ACTION_MOVE: { @@ -3917,6 +4236,10 @@ public class WebView extends AbsoluteLayout } } + if (mDragTrackerHandler != null) { + mDragTrackerHandler.dragTo(x, y); + } + if (done) { // keep the scrollbar on the screen even there is no scroll awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), @@ -3928,6 +4251,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 @@ -4003,26 +4330,42 @@ public class WebView extends AbsoluteLayout break; } case MotionEvent.ACTION_CANCEL: { - // 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 - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - if (mTouchMode == TOUCH_DRAG_MODE) { - WebViewCore.resumeUpdate(mWebViewCore); + if (mDragTrackerHandler != null) { + mDragTrackerHandler.stopDrag(); + mDragTrackerHandler = null; } - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mTouchMode = TOUCH_DONE_MODE; - nativeHideCursor(); + cancelTouch(); break; } } return true; } + private void startTouch(float x, float y, long eventTime) { + mLastTouchX = x; + mLastTouchY = y; + mLastTouchTime = eventTime; + mVelocityTracker = VelocityTracker.obtain(); + mSnapScrollMode = SNAP_NONE; + } + + private void cancelTouch() { + // 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 + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + if (mTouchMode == TOUCH_DRAG_MODE) { + WebViewCore.resumeUpdate(mWebViewCore); + } + mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); + mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); + mTouchMode = TOUCH_DONE_MODE; + nativeHideCursor(); + } + private long mTrackballFirstTime = 0; private long mTrackballLastTime = 0; private float mTrackballRemainsX = 0.0f; @@ -4297,7 +4640,7 @@ public class WebView extends AbsoluteLayout private int computeMaxScrollY() { int maxContentH = computeVerticalScrollRange() + getTitleHeight(); - return Math.max(maxContentH - getHeight(), getTitleHeight()); + return Math.max(maxContentH - getViewHeightWithTitle(), getTitleHeight()); } public void flingScroll(int vx, int vy) { @@ -4382,7 +4725,7 @@ public class WebView extends AbsoluteLayout scale = mDefaultScale; } - setNewZoomScale(scale, false); + setNewZoomScale(scale, true, false); if (oldScale != mActualScale) { // use mZoomPickerScale to see zoom preview first @@ -4390,9 +4733,6 @@ public class WebView extends AbsoluteLayout mInvInitialZoomScale = 1.0f / oldScale; mInvFinalZoomScale = 1.0f / mActualScale; mZoomScale = mActualScale; - if (!mInZoomOverview) { - mLastScale = scale; - } invalidate(); return true; } else { @@ -4490,18 +4830,13 @@ public class WebView extends AbsoluteLayout public boolean zoomIn() { // TODO: alternatively we can disallow this during draw history mode switchOutDrawHistory(); + mInZoomOverview = false; // Center zooming to the center of the screen. - if (mInZoomOverview) { - // if in overview mode, bring it back to normal mode - mLastTouchX = getViewWidth() * .5f; - mLastTouchY = getViewHeight() * .5f; - doDoubleTap(); - return true; - } else { - mZoomCenterX = getViewWidth() * .5f; - mZoomCenterY = getViewHeight() * .5f; - return zoomWithPreview(mActualScale * 1.25f); - } + mZoomCenterX = getViewWidth() * .5f; + mZoomCenterY = getViewHeight() * .5f; + mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); + mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); + return zoomWithPreview(mActualScale * 1.25f); } /** @@ -4511,18 +4846,12 @@ public class WebView extends AbsoluteLayout public boolean zoomOut() { // TODO: alternatively we can disallow this during draw history mode switchOutDrawHistory(); - float scale = mActualScale * 0.8f; - if (scale < (mMinZoomScale + 0.1f) - && mWebViewCore.getSettings().getUseWideViewPort()) { - // when zoom out to min scale, switch to overview mode - doDoubleTap(); - return true; - } else { - // Center zooming to the center of the screen. - mZoomCenterX = getViewWidth() * .5f; - mZoomCenterY = getViewHeight() * .5f; - return zoomWithPreview(scale); - } + // Center zooming to the center of the screen. + mZoomCenterX = getViewWidth() * .5f; + mZoomCenterY = getViewHeight() * .5f; + mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); + mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); + return zoomWithPreview(mActualScale * 0.8f); } private void updateSelection() { @@ -4614,6 +4943,7 @@ public class WebView extends AbsoluteLayout View v = mWebTextView; int x = viewToContentX((v.getLeft() + v.getRight()) >> 1); int y = viewToContentY((v.getTop() + v.getBottom()) >> 1); + displaySoftKeyboard(true); nativeTextInputMotionUp(x, y); } } @@ -4637,15 +4967,21 @@ public class WebView extends AbsoluteLayout } } + // Rule for double tap: + // 1. if the current scale is not same as the text wrap scale and layout + // algorithm is NARROW_COLUMNS, fit to column; + // 2. if the current state is not overview mode, change to overview mode; + // 3. if the current state is overview mode, change to default scale. private void doDoubleTap() { if (mWebViewCore.getSettings().getUseWideViewPort() == false) { return; } mZoomCenterX = mLastTouchX; mZoomCenterY = mLastTouchY; - mInZoomOverview = !mInZoomOverview; - // remove the zoom control after double tap + mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); + mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); WebSettings settings = getSettings(); + // remove the zoom control after double tap if (settings.getBuiltInZoomControls()) { if (mZoomButtonsController.isVisible()) { mZoomButtonsController.setVisible(false); @@ -4659,22 +4995,45 @@ public class WebView extends AbsoluteLayout } } settings.setDoubleTapToastCount(0); - if (mInZoomOverview) { - // Force the titlebar fully reveal in overview mode - if (mScrollY < getTitleHeight()) mScrollY = 0; - zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth); + boolean zoomToDefault = false; + if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS) + && (Math.abs(mActualScale - mTextWrapScale) >= 0.01f)) { + setNewZoomScale(mActualScale, true, true); + float overviewScale = (float) getViewWidth() / mZoomOverviewWidth; + if (Math.abs(mActualScale - overviewScale) < 0.01f) { + mInZoomOverview = true; + } + } else if (!mInZoomOverview) { + float newScale = (float) getViewWidth() / mZoomOverviewWidth; + if (Math.abs(mActualScale - newScale) >= 0.01f) { + mInZoomOverview = true; + // Force the titlebar fully reveal in overview mode + if (mScrollY < getTitleHeight()) mScrollY = 0; + zoomWithPreview(newScale); + } else if (Math.abs(mActualScale - mDefaultScale) >= 0.01f) { + zoomToDefault = true; + } } else { - // mLastTouchX and mLastTouchY are the point in the current viewport - int contentX = viewToContentX((int) mLastTouchX + mScrollX); - int contentY = viewToContentY((int) mLastTouchY + mScrollY); - int left = nativeGetBlockLeftEdge(contentX, contentY, mActualScale); + zoomToDefault = true; + } + if (zoomToDefault) { + mInZoomOverview = false; + int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale); if (left != NO_LEFTEDGE) { - // add a 5pt padding to the left edge. Re-calculate the zoom - // center so that the new scroll x will be on the left edge. - mZoomCenterX = left < 5 ? 0 : (left - 5) * mLastScale - * mActualScale / (mLastScale - mActualScale); + // add a 5pt padding to the left edge. + int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5)) + - mScrollX; + // Re-calculate the zoom center so that the new scroll x will be + // on the left edge. + if (viewLeft > 0) { + mZoomCenterX = viewLeft * mDefaultScale + / (mDefaultScale - mActualScale); + } else { + scrollBy(viewLeft, 0); + mZoomCenterX = 0; + } } - zoomWithPreview(mLastScale); + zoomWithPreview(mDefaultScale); } } @@ -4880,9 +5239,10 @@ public class WebView extends AbsoluteLayout @Override public void handleMessage(Message msg) { if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what - > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) - : HandlerDebugString[msg.what - REMEMBER_PASSWORD]); + Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD + || msg.what > SHOW_RECT_MSG_ID ? Integer + .toString(msg.what) : HandlerDebugString[msg.what + - REMEMBER_PASSWORD]); } if (mWebViewCore == null) { // after WebView's destroy() is called, skip handling messages. @@ -4956,6 +5316,14 @@ public class WebView extends AbsoluteLayout case SPAWN_SCROLL_TO_MSG_ID: spawnContentScrollTo(msg.arg1, msg.arg2); break; + case UPDATE_ZOOM_RANGE: { + WebViewCore.RestoreState restoreState + = (WebViewCore.RestoreState) msg.obj; + // mScrollX contains the new minPrefWidth + updateZoomRange(restoreState, getViewWidth(), + restoreState.mScrollX, false); + break; + } case NEW_PICTURE_MSG_ID: { WebSettings settings = mWebViewCore.getSettings(); // called for new content @@ -4967,42 +5335,31 @@ public class WebView extends AbsoluteLayout WebViewCore.RestoreState restoreState = draw.mRestoreState; if (restoreState != null) { mInZoomOverview = false; - mLastScale = restoreState.mTextWrapScale; - if (restoreState.mMinScale == 0) { - if (restoreState.mMobileSite) { - if (draw.mMinPrefWidth > - Math.max(0, draw.mViewPoint.x)) { - mMinZoomScale = (float) viewWidth - / draw.mMinPrefWidth; - mMinZoomScaleFixed = false; - } else { - mMinZoomScale = restoreState.mDefaultScale; - mMinZoomScaleFixed = true; - } + updateZoomRange(restoreState, viewSize.x, + draw.mMinPrefWidth, true); + if (mInitialScaleInPercent > 0) { + setNewZoomScale(mInitialScaleInPercent / 100.0f, + mInitialScaleInPercent != mTextWrapScale * 100, + false); + } else if (restoreState.mViewScale > 0) { + mTextWrapScale = restoreState.mTextWrapScale; + setNewZoomScale(restoreState.mViewScale, false, + false); + } else { + mInZoomOverview = useWideViewport + && settings.getLoadWithOverviewMode(); + float scale; + if (mInZoomOverview) { + scale = (float) viewWidth + / WebViewCore.DEFAULT_VIEWPORT_WIDTH; } else { - mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; - mMinZoomScaleFixed = false; + scale = restoreState.mTextWrapScale; } - } else { - mMinZoomScale = restoreState.mMinScale; - mMinZoomScaleFixed = true; - } - if (restoreState.mMaxScale == 0) { - mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; - } else { - mMaxZoomScale = restoreState.mMaxScale; + setNewZoomScale(scale, Math.abs(scale + - mTextWrapScale) >= 0.01f, false); } - setNewZoomScale(mLastScale, false); setContentScrollTo(restoreState.mScrollX, restoreState.mScrollY); - if (useWideViewport - && settings.getLoadWithOverviewMode()) { - if (restoreState.mViewScale == 0 - || (restoreState.mMobileSite - && mMinZoomScale < restoreState.mDefaultScale)) { - mInZoomOverview = true; - } - } // As we are on a new page, remove the WebTextView. This // is necessary for page loads driven by webkit, and in // particular when the user was on a password field, so @@ -5028,8 +5385,9 @@ public class WebView extends AbsoluteLayout mPictureListener.onNewPicture(WebView.this, capturePicture()); } if (useWideViewport) { - mZoomOverviewWidth = Math.max(draw.mMinPrefWidth, - draw.mViewPoint.x); + mZoomOverviewWidth = Math.max( + (int) (viewWidth / mDefaultScale), Math.max( + draw.mMinPrefWidth, draw.mViewPoint.x)); } if (!mMinZoomScaleFixed) { mMinZoomScale = (float) viewWidth / mZoomOverviewWidth; @@ -5040,7 +5398,8 @@ public class WebView extends AbsoluteLayout if (Math.abs((viewWidth * mInvActualScale) - mZoomOverviewWidth) > 1) { setNewZoomScale((float) viewWidth - / mZoomOverviewWidth, false); + / mZoomOverviewWidth, Math.abs(mActualScale + - mTextWrapScale) < 0.01f, false); } } break; @@ -5172,6 +5531,43 @@ public class WebView extends AbsoluteLayout } break; + case SHOW_RECT_MSG_ID: + WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj; + int x = mScrollX; + int left = contentToViewDimension(data.mLeft); + int width = contentToViewDimension(data.mWidth); + int maxWidth = contentToViewDimension(data.mContentWidth); + int viewWidth = getViewWidth(); + if (width < viewWidth) { + // center align + x += left + width / 2 - mScrollX - viewWidth / 2; + } else { + x += (int) (left + data.mXPercentInDoc * width + - mScrollX - data.mXPercentInView * viewWidth); + } + // use the passing content width to cap x as the current + // mContentWidth may not be updated yet + x = Math.max(0, + (Math.min(maxWidth, x + viewWidth)) - viewWidth); + int y = mScrollY; + int top = contentToViewDimension(data.mTop); + int height = contentToViewDimension(data.mHeight); + int maxHeight = contentToViewDimension(data.mContentHeight); + int viewHeight = getViewHeight(); + if (height < viewHeight) { + // middle align + y += top + height / 2 - mScrollY - viewHeight / 2; + } else { + y += (int) (top + data.mYPercentInDoc * height + - mScrollY - data.mYPercentInView * viewHeight); + } + // use the passing content height to cap y as the current + // mContentHeight may not be updated yet + y = Math.max(0, + (Math.min(maxHeight, y + viewHeight) - viewHeight)); + scrollTo(x, y); + break; + default: super.handleMessage(msg); break; @@ -5411,6 +5807,37 @@ public class WebView extends AbsoluteLayout new InvokeListBox(array, enabledArray, selectedArray)); } + private void updateZoomRange(WebViewCore.RestoreState restoreState, + int viewWidth, int minPrefWidth, boolean updateZoomOverview) { + if (restoreState.mMinScale == 0) { + if (restoreState.mMobileSite) { + if (minPrefWidth > Math.max(0, viewWidth)) { + mMinZoomScale = (float) viewWidth / minPrefWidth; + mMinZoomScaleFixed = false; + if (updateZoomOverview) { + WebSettings settings = getSettings(); + mInZoomOverview = settings.getUseWideViewPort() && + settings.getLoadWithOverviewMode(); + } + } else { + mMinZoomScale = restoreState.mDefaultScale; + mMinZoomScaleFixed = true; + } + } else { + mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; + mMinZoomScaleFixed = false; + } + } else { + mMinZoomScale = restoreState.mMinScale; + mMinZoomScaleFixed = true; + } + if (restoreState.mMaxScale == 0) { + mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; + } else { + mMaxZoomScale = restoreState.mMaxScale; + } + } + /* * Request a dropdown menu for a listbox with single selection or a single * <select> element. diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index a5a4852..6aae794 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -447,8 +447,8 @@ final class WebViewCore { should this be called nativeSetViewPortSize? */ private native void nativeSetSize(int width, int height, int screenWidth, - float scale, int realScreenWidth, int screenHeight, - boolean ignoreHeight); + float scale, int realScreenWidth, int screenHeight, int anchorX, + int anchorY, boolean ignoreHeight); private native int nativeGetContentMinPrefWidth(); @@ -961,6 +961,7 @@ final class WebViewCore { (WebView.ViewSizeData) msg.obj; viewSizeChanged(data.mWidth, data.mHeight, data.mTextWrapWidth, data.mScale, + data.mAnchorX, data.mAnchorY, data.mIgnoreHeight); break; } @@ -1483,7 +1484,7 @@ final class WebViewCore { // notify webkit that our virtual view size changed size (after inv-zoom) private void viewSizeChanged(int w, int h, int textwrapWidth, float scale, - boolean ignoreHeight) { + int anchorX, int anchorY, boolean ignoreHeight) { if (DebugFlags.WEB_VIEW_CORE) { Log.v(LOGTAG, "viewSizeChanged w=" + w + "; h=" + h + "; textwrapWidth=" + textwrapWidth + "; scale=" + scale); @@ -1514,12 +1515,14 @@ final class WebViewCore { width = Math.max(w, Math.max(DEFAULT_VIEWPORT_WIDTH, nativeGetContentMinPrefWidth())); } - } else { + } else if (mViewportWidth > 0) { width = Math.max(w, mViewportWidth); + } else { + width = textwrapWidth; } } nativeSetSize(width, width == w ? h : Math.round((float) width * h / w), - textwrapWidth, scale, w, h, ignoreHeight); + textwrapWidth, scale, w, h, anchorX, anchorY, ignoreHeight); // Remember the current width and height boolean needInvalidate = (mCurrentViewWidth == 0); mCurrentViewWidth = w; @@ -1643,6 +1646,8 @@ final class WebViewCore { final DrawFilter mZoomFilter = new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG); + final DrawFilter mScrollFilter = + new PaintFlagsDrawFilter(SCROLL_BITS, 0); /* package */ void drawContentPicture(Canvas canvas, int color, boolean animatingZoom, @@ -1651,7 +1656,7 @@ final class WebViewCore { if (animatingZoom) { df = mZoomFilter; } else if (animatingScroll) { - df = null; + df = mScrollFilter; } canvas.setDrawFilter(df); boolean tookTooLong = nativeDrawContent(canvas, color); @@ -1926,7 +1931,19 @@ final class WebViewCore { } // if mViewportWidth is 0, it means device-width, always update. - if (mViewportWidth != 0 && !updateRestoreState) return; + if (mViewportWidth != 0 && !updateRestoreState) { + RestoreState restoreState = new RestoreState(); + restoreState.mMinScale = mViewportMinimumScale / 100.0f; + restoreState.mMaxScale = mViewportMaximumScale / 100.0f; + restoreState.mDefaultScale = adjust; + // as mViewportWidth is not 0, it is not mobile site. + restoreState.mMobileSite = false; + // for non-mobile site, we don't need minPrefWidth, set it as 0 + restoreState.mScrollX = 0; + Message.obtain(mWebView.mPrivateHandler, + WebView.UPDATE_ZOOM_RANGE, restoreState).sendToTarget(); + return; + } // now notify webview // webViewWidth refers to the width in the view system @@ -1953,14 +1970,12 @@ final class WebViewCore { mRestoreState.mScrollY = mRestoredY; mRestoreState.mMobileSite = (0 == mViewportWidth); if (mRestoredScale > 0) { + mRestoreState.mViewScale = mRestoredScale / 100.0f; if (mRestoredScreenWidthScale > 0) { mRestoreState.mTextWrapScale = mRestoredScreenWidthScale / 100.0f; - // 0 will trigger WebView to turn on zoom overview mode - mRestoreState.mViewScale = 0; } else { - mRestoreState.mViewScale = mRestoreState.mTextWrapScale = - mRestoredScale / 100.0f; + mRestoreState.mTextWrapScale = mRestoreState.mViewScale; } } else { if (mViewportInitialScale > 0) { @@ -1993,6 +2008,7 @@ final class WebViewCore { data.mTextWrapWidth = data.mWidth; data.mScale = -1.0f; data.mIgnoreHeight = false; + data.mAnchorX = data.mAnchorY = 0; // send VIEW_SIZE_CHANGED to the front of the queue so that we can // avoid pushing the wrong picture to the WebView side. If there is // a VIEW_SIZE_CHANGED in the queue, probably from WebView side, @@ -2021,6 +2037,7 @@ final class WebViewCore { data.mTextWrapWidth = Math.round(webViewWidth / mRestoreState.mTextWrapScale); data.mIgnoreHeight = false; + data.mAnchorX = data.mAnchorY = 0; // send VIEW_SIZE_CHANGED to the front of the queue so that we // can avoid pushing the wrong picture to the WebView side. mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED); @@ -2177,6 +2194,40 @@ final class WebViewCore { childView.removeView(); } + // called by JNI + static class ShowRectData { + int mLeft; + int mTop; + int mWidth; + int mHeight; + int mContentWidth; + int mContentHeight; + float mXPercentInDoc; + float mXPercentInView; + float mYPercentInDoc; + float mYPercentInView; + } + + private void showRect(int left, int top, int width, int height, + int contentWidth, int contentHeight, float xPercentInDoc, + float xPercentInView, float yPercentInDoc, float yPercentInView) { + if (mWebView != null) { + ShowRectData data = new ShowRectData(); + data.mLeft = left; + data.mTop = top; + data.mWidth = width; + data.mHeight = height; + data.mContentWidth = contentWidth; + data.mContentHeight = contentHeight; + data.mXPercentInDoc = xPercentInDoc; + data.mXPercentInView = xPercentInView; + data.mYPercentInDoc = yPercentInDoc; + data.mYPercentInView = yPercentInView; + Message.obtain(mWebView.mPrivateHandler, WebView.SHOW_RECT_MSG_ID, + data).sendToTarget(); + } + } + private native void nativePause(); private native void nativeResume(); private native void nativeFreeMemory(); diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 6e10811..110e4f8 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -27,6 +27,7 @@ import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteStatement; import android.util.Log; import android.webkit.CookieManager.Cookie; @@ -174,7 +175,16 @@ public class WebViewDatabase { public static synchronized WebViewDatabase getInstance(Context context) { if (mInstance == null) { mInstance = new WebViewDatabase(); - mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null); + try { + mDatabase = context + .openOrCreateDatabase(DATABASE_FILE, 0, null); + } catch (SQLiteException e) { + // try again by deleting the old db and create a new one + if (context.deleteDatabase(DATABASE_FILE)) { + mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, + null); + } + } // mDatabase should not be null, // the only case is RequestAPI test has problem to create db @@ -194,8 +204,16 @@ public class WebViewDatabase { mDatabase.setLockingEnabled(false); } - mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE, - 0, null); + try { + mCacheDatabase = context.openOrCreateDatabase( + CACHE_DATABASE_FILE, 0, null); + } catch (SQLiteException e) { + // try again by deleting the old db and create a new one + if (context.deleteDatabase(CACHE_DATABASE_FILE)) { + mCacheDatabase = context.openOrCreateDatabase( + CACHE_DATABASE_FILE, 0, null); + } + } // mCacheDatabase should not be null, // the only case is RequestAPI test has problem to create db diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java index 2fc29bc..5c05170 100644 --- a/core/java/android/widget/ImageButton.java +++ b/core/java/android/widget/ImageButton.java @@ -57,6 +57,11 @@ import java.util.Map; * based on the state of the button and the corresponding images * defined in the XML.</p> * + * <p>The order of the {@code <item>} elements is important because they are + * evaluated in order. This is why the "normal" button image comes last, because + * it will only be applied after {@code android:state_pressed} and {@code + * android:state_focused} have both evaluated false.</p> + * * <p><strong>XML attributes</strong></p> * <p> * See {@link android.R.styleable#ImageView Button Attributes}, diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index d86b674..e4cc609 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1328,7 +1328,7 @@ public class PopupWindow { getKeyDispatcherState().startTracking(event, this); return true; } else if (event.getAction() == KeyEvent.ACTION_UP - && event.isTracking() && !event.isCanceled()) { + && getKeyDispatcherState().isTracking(event) && !event.isCanceled()) { dismiss(); return true; } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 6771711..b847e57 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -23,6 +23,7 @@ import android.content.IntentSender; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.graphics.PorterDuff; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Parcel; @@ -136,10 +137,15 @@ 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]; + v.getLocationOnScreen(pos); + Intent intent = new Intent(); + intent.setSourceBounds(new Rect(pos[0], pos[1], + pos[0]+v.getWidth(), pos[1]+v.getHeight())); try { // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? v.getContext().startIntentSender( - pendingIntent.getIntentSender(), null, + pendingIntent.getIntentSender(), intent, Intent.FLAG_ACTIVITY_NEW_TASK, Intent.FLAG_ACTIVITY_NEW_TASK, 0); } catch (IntentSender.SendIntentException e) { @@ -457,6 +463,46 @@ public class RemoteViews implements Parcelable, Filter { } } + /** + * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the + * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()} + * when null. This allows users to build "nested" {@link RemoteViews}. + */ + private class ViewGroupAction extends Action { + public ViewGroupAction(int viewId, RemoteViews nestedViews) { + this.viewId = viewId; + this.nestedViews = nestedViews; + } + + public ViewGroupAction(Parcel parcel) { + viewId = parcel.readInt(); + nestedViews = parcel.readParcelable(null); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + dest.writeParcelable(nestedViews, 0 /* no flags */); + } + + @Override + public void apply(View root) { + final Context context = root.getContext(); + final ViewGroup target = (ViewGroup) root.findViewById(viewId); + if (nestedViews != null) { + // Inflate nested views and add as children + target.addView(nestedViews.apply(context, target)); + } else if (target != null) { + // Clear all children when nested views omitted + target.removeAllViews(); + } + } + + int viewId; + RemoteViews nestedViews; + + public final static int TAG = 4; + } /** * Create a new RemoteViews object that will display the views contained @@ -493,6 +539,9 @@ public class RemoteViews implements Parcelable, Filter { case ReflectionAction.TAG: mActions.add(new ReflectionAction(parcel)); break; + case ViewGroupAction.TAG: + mActions.add(new ViewGroupAction(parcel)); + break; default: throw new ActionException("Tag " + tag + " not found"); } @@ -519,7 +568,31 @@ public class RemoteViews implements Parcelable, Filter { } mActions.add(a); } - + + /** + * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the + * given {@link RemoteViews}. This allows users to build "nested" + * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may + * recycle layouts, use {@link #removeAllViews(int)} to clear any existing + * children. + * + * @param viewId The id of the parent {@link ViewGroup} to add child into. + * @param nestedView {@link RemoteViews} that describes the child. + */ + public void addView(int viewId, RemoteViews nestedView) { + addAction(new ViewGroupAction(viewId, nestedView)); + } + + /** + * Equivalent to calling {@link ViewGroup#removeAllViews()}. + * + * @param viewId The id of the parent {@link ViewGroup} to remove all + * children from. + */ + public void removeAllViews(int viewId) { + addAction(new ViewGroupAction(viewId, null)); + } + /** * Equivalent to calling View.setVisibility * diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java index b6a49e5..436b79b 100644 --- a/core/java/android/widget/SimpleCursorAdapter.java +++ b/core/java/android/widget/SimpleCursorAdapter.java @@ -74,7 +74,7 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { * @param context The context where the ListView associated with this * SimpleListItemFactory is running * @param layout resource identifier of a layout file that defines the views - * for this list item. Thelayout file should include at least + * for this list item. The layout file should include at least * those named views defined in "to" * @param c The database cursor. Can be null if the cursor is not available yet. * @param from A list of column names representing the data to bind to the UI. Can be null diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8980c17..6418dad 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4531,6 +4531,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Now use the delta to determine the actual amount of text // we need. partialEndOffset += delta; + if (partialStartOffset > N) { + partialStartOffset = N; + } else if (partialStartOffset < 0) { + partialStartOffset = 0; + } if (partialEndOffset > N) { partialEndOffset = N; } else if (partialEndOffset < 0) { @@ -6298,7 +6303,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (AccessibilityManager.getInstance(mContext).isEnabled() + && !isPasswordInputType(mInputType)) { mBeforeText = buffer.toString(); } @@ -6531,6 +6537,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Reset this state; it will be re-set if super.onTouchEvent // causes focus to move to the view. mTouchFocusSelected = false; + mScrolled = false; } final boolean superResult = super.onTouchEvent(event); @@ -6547,10 +6554,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) { - if (action == MotionEvent.ACTION_DOWN) { - mScrolled = false; - } - boolean handled = false; int oldSelStart = Selection.getSelectionStart(mText); @@ -6972,9 +6975,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - boolean isPassword = - (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) == - (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + final boolean isPassword = isPasswordInputType(mInputType); if (!isPassword) { CharSequence text = getText(); diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index 33123c8..907cfb3 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -43,7 +43,7 @@ public class ViewAnimator extends FrameLayout { public ViewAnimator(Context context) { super(context); - initViewAnimator(); + initViewAnimator(context, null); } public ViewAnimator(Context context, AttributeSet attrs) { @@ -61,11 +61,28 @@ public class ViewAnimator extends FrameLayout { } a.recycle(); - initViewAnimator(); + initViewAnimator(context, attrs); } - private void initViewAnimator() { - mMeasureAllChildren = true; + /** + * Initialize this {@link ViewAnimator}, possibly setting + * {@link #setMeasureAllChildren(boolean)} based on {@link FrameLayout} flags. + */ + private void initViewAnimator(Context context, AttributeSet attrs) { + if (attrs == null) { + // For compatibility, always measure children when undefined. + mMeasureAllChildren = true; + return; + } + + // For compatibility, default to measure children, but allow XML + // attribute to override. + final TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.FrameLayout); + final boolean measureAllChildren = a.getBoolean( + com.android.internal.R.styleable.FrameLayout_measureAllChildren, true); + setMeasureAllChildren(measureAllChildren); + a.recycle(); } /** diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index 8a7946b..aee25b0 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -16,12 +16,15 @@ package android.widget; - +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.TypedArray; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; +import android.util.Log; import android.widget.RemoteViews.RemoteView; /** @@ -30,10 +33,22 @@ import android.widget.RemoteViews.RemoteView; * requested, can automatically flip between each child at a regular interval. * * @attr ref android.R.styleable#ViewFlipper_flipInterval + * @attr ref android.R.styleable#ViewFlipper_autoStart */ +@RemoteView public class ViewFlipper extends ViewAnimator { - private int mFlipInterval = 3000; - private boolean mKeepFlipping = false; + private static final String TAG = "ViewFlipper"; + private static final boolean LOGD = true; + + private static final int DEFAULT_INTERVAL = 3000; + + private int mFlipInterval = DEFAULT_INTERVAL; + private boolean mAutoStart = false; + + private boolean mRunning = false; + private boolean mStarted = false; + private boolean mVisible = false; + private boolean mUserPresent = true; public ViewFlipper(Context context) { super(context); @@ -44,14 +59,62 @@ public class ViewFlipper extends ViewAnimator { TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewFlipper); - mFlipInterval = a.getInt(com.android.internal.R.styleable.ViewFlipper_flipInterval, - 3000); + mFlipInterval = a.getInt( + com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL); + mAutoStart = a.getBoolean( + com.android.internal.R.styleable.ViewFlipper_autoStart, false); a.recycle(); } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + mUserPresent = false; + updateRunning(); + } else if (Intent.ACTION_USER_PRESENT.equals(action)) { + mUserPresent = true; + updateRunning(); + } + } + }; + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + // Listen for broadcasts related to user-presence + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_USER_PRESENT); + getContext().registerReceiver(mReceiver, filter); + + if (mAutoStart) { + // Automatically start when requested + startFlipping(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mVisible = false; + + getContext().unregisterReceiver(mReceiver); + updateRunning(); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mVisible = visibility == VISIBLE; + updateRunning(); + } + /** * How long to wait before flipping to the next view - * + * * @param milliseconds * time in milliseconds */ @@ -64,26 +127,61 @@ public class ViewFlipper extends ViewAnimator { * Start a timer to cycle through child views */ public void startFlipping() { - if (!mKeepFlipping) { - mKeepFlipping = true; - showOnly(mWhichChild); - Message msg = mHandler.obtainMessage(FLIP_MSG); - mHandler.sendMessageDelayed(msg, mFlipInterval); - } + mStarted = true; + updateRunning(); } /** * No more flips */ public void stopFlipping() { - mKeepFlipping = false; + mStarted = false; + updateRunning(); + } + + /** + * Internal method to start or stop dispatching flip {@link Message} based + * on {@link #mRunning} and {@link #mVisible} state. + */ + private void updateRunning() { + boolean running = mVisible && mStarted && mUserPresent; + if (running != mRunning) { + if (running) { + showOnly(mWhichChild); + Message msg = mHandler.obtainMessage(FLIP_MSG); + mHandler.sendMessageDelayed(msg, mFlipInterval); + } else { + mHandler.removeMessages(FLIP_MSG); + } + mRunning = running; + } + if (LOGD) { + Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted + + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning); + } } /** * Returns true if the child views are flipping. */ public boolean isFlipping() { - return mKeepFlipping; + return mStarted; + } + + /** + * Set if this view automatically calls {@link #startFlipping()} when it + * becomes attached to a window. + */ + public void setAutoStart(boolean autoStart) { + mAutoStart = autoStart; + } + + /** + * Returns true if this view automatically calls {@link #startFlipping()} + * when it becomes attached to a window. + */ + public boolean isAutoStart() { + return mAutoStart; } private final int FLIP_MSG = 1; @@ -92,7 +190,7 @@ public class ViewFlipper extends ViewAnimator { @Override public void handleMessage(Message msg) { if (msg.what == FLIP_MSG) { - if (mKeepFlipping) { + if (mRunning) { showNext(); msg = obtainMessage(FLIP_MSG); sendMessageDelayed(msg, mFlipInterval); diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index e55fbb8..c683e71 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -546,6 +546,11 @@ public class ZoomButtonsController implements View.OnTouchListener { public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); + if (event.getPointerCount() > 1) { + // ZoomButtonsController doesn't handle mutitouch. Give up control. + return false; + } + if (mReleaseTouchListenerOnUp) { // The controls were dismissed but we need to throw away all events until the up if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { |