summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountManagerResponse.java3
-rw-r--r--core/java/android/accounts/AccountManagerService.java3
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java3
-rw-r--r--core/java/android/annotation/SdkConstant.java2
-rw-r--r--core/java/android/app/ActivityThread.java80
-rw-r--r--core/java/android/app/ApplicationThreadNative.java15
-rw-r--r--core/java/android/app/IApplicationThread.java2
-rw-r--r--core/java/android/app/SearchManager.java27
-rw-r--r--core/java/android/app/WallpaperInfo.java4
-rw-r--r--core/java/android/app/WallpaperManager.java31
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java25
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java44
-rw-r--r--core/java/android/bluetooth/BluetoothClass.java4
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java8
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java13
-rw-r--r--core/java/android/bluetooth/BluetoothServerSocket.java8
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java9
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl1
-rw-r--r--core/java/android/bluetooth/IBluetoothA2dp.aidl1
-rw-r--r--core/java/android/bluetooth/package.html90
-rw-r--r--core/java/android/content/AbstractSyncableContentProvider.java4
-rw-r--r--core/java/android/content/Context.java1
-rw-r--r--core/java/android/content/Intent.java134
-rw-r--r--core/java/android/content/SyncManager.java17
-rw-r--r--core/java/android/content/pm/ActivityInfo.java21
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java30
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java12
-rw-r--r--core/java/android/content/pm/PackageManager.java92
-rw-r--r--core/java/android/content/pm/PackageParser.java11
-rw-r--r--core/java/android/content/res/AssetManager.java53
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java1
-rw-r--r--core/java/android/hardware/Camera.java38
-rw-r--r--core/java/android/os/Build.java9
-rw-r--r--core/java/android/os/IMountService.aidl5
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/LocalPowerManager.java2
-rw-r--r--core/java/android/os/Power.java18
-rw-r--r--core/java/android/os/PowerManager.java29
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java6
-rw-r--r--core/java/android/provider/Checkin.java1
-rw-r--r--core/java/android/provider/ContactsContract.java2489
-rw-r--r--core/java/android/provider/Settings.java23
-rw-r--r--core/java/android/server/BluetoothA2dpService.java32
-rw-r--r--core/java/android/server/BluetoothEventLoop.java31
-rw-r--r--core/java/android/server/BluetoothService.java218
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java43
-rw-r--r--core/java/android/service/wallpaper/WallpaperSettingsActivity.java1
-rw-r--r--core/java/android/test/TimedTest.java32
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java2
-rw-r--r--core/java/android/text/method/Touch.java12
-rw-r--r--core/java/android/view/IWindowManager.aidl4
-rw-r--r--core/java/android/view/ScaleGestureDetector.java460
-rw-r--r--core/java/android/view/VelocityTracker.java147
-rw-r--r--core/java/android/view/View.java7
-rw-r--r--core/java/android/view/ViewRoot.java4
-rw-r--r--core/java/android/view/WindowManagerPolicy.java2
-rw-r--r--core/java/android/webkit/CacheManager.java5
-rw-r--r--core/java/android/webkit/CallbackProxy.java2
-rw-r--r--core/java/android/webkit/DebugFlags.java2
-rwxr-xr-xcore/java/android/webkit/GeolocationPermissions.java42
-rw-r--r--core/java/android/webkit/WebChromeClient.java23
-rw-r--r--core/java/android/webkit/WebSettings.java2
-rw-r--r--core/java/android/webkit/WebView.java779
-rw-r--r--core/java/android/webkit/WebViewCore.java73
-rw-r--r--core/java/android/webkit/WebViewDatabase.java24
-rw-r--r--core/java/android/widget/ImageButton.java5
-rw-r--r--core/java/android/widget/PopupWindow.java2
-rw-r--r--core/java/android/widget/RemoteViews.java77
-rw-r--r--core/java/android/widget/SimpleCursorAdapter.java2
-rw-r--r--core/java/android/widget/TextView.java17
-rw-r--r--core/java/android/widget/ViewAnimator.java25
-rw-r--r--core/java/android/widget/ViewFlipper.java128
-rw-r--r--core/java/android/widget/ZoomButtonsController.java5
-rw-r--r--core/java/com/android/internal/app/ShutdownThread.java16
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java2
-rw-r--r--core/java/com/android/internal/service/wallpaper/ImageWallpaper.java15
-rw-r--r--core/java/com/android/internal/widget/ContactHeaderWidget.java170
-rw-r--r--core/java/com/android/internal/widget/DigitalClock.java191
-rw-r--r--core/java/com/android/internal/widget/SlidingTab.java849
79 files changed, 6054 insertions, 768 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&mdash;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&mdash;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(&quot;limit&quot;, &quot;3&quot;)
+ * .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, &quot;Mike Sullivan&quot;);
+ * 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&lt;ContentProviderOperation&gt; 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, &quot;Mike Sullivan&quot;)
+ * .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 + "&lt;&gt;0", null, null, null);
+ * ...
+ * Cursor c2 = getContentResolver().query(rawContactUri,
+ * RawContacts.DELETED + "&lt;&gt;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&lt;ContentProviderOperation&gt; 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&lt;ContentProviderOperation&gt; 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&lt;ContentProviderOperation&gt; 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 + &quot;=?&quot; + " 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 + &quot;=?&quot; + " 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&lt;ContentProviderOperation&gt; 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, &quot;Bob Parr&quot;)
+ * .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 &lt;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) {
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index 2060cf8..c110f95 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -32,6 +32,7 @@ import android.os.RemoteException;
import android.os.Power;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.IMountService;
import com.android.internal.telephony.ITelephony;
import android.util.Log;
@@ -189,6 +190,10 @@ public final class ShutdownThread extends Thread {
final IBluetooth bluetooth =
IBluetooth.Stub.asInterface(ServiceManager.checkService(
BluetoothAdapter.BLUETOOTH_SERVICE));
+
+ final IMountService mount =
+ IMountService.Stub.asInterface(
+ ServiceManager.checkService("mount"));
try {
bluetoothOff = bluetooth == null ||
@@ -241,6 +246,17 @@ public final class ShutdownThread extends Thread {
SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
}
+ // Shutdown MountService to ensure media is in a safe state
+ try {
+ if (mount != null) {
+ mount.shutdown();
+ } else {
+ Log.w(TAG, "MountService unavailable for shutdown");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception during MountService shutdown", e);
+ }
+
//shutdown power
Log.i(TAG, "Performing low-level shutdown...");
Power.shutdown();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8698cb7..5199ada 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -57,7 +57,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 41;
+ private static final int VERSION = 42;
private static int sNumSpeedSteps;
diff --git a/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java b/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java
index c09ecfb..251ecbc 100644
--- a/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java
+++ b/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java
@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable;
import android.os.HandlerThread;
import android.os.Process;
import android.service.wallpaper.WallpaperService;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.content.Context;
@@ -134,8 +135,8 @@ public class ImageWallpaper extends WallpaperService {
final Drawable background = mBackground;
final int dw = frame.width();
final int dh = frame.height();
- final int bw = mBackground.getIntrinsicWidth();
- final int bh = mBackground.getIntrinsicHeight();
+ final int bw = background != null ? background.getIntrinsicWidth() : 0;
+ final int bh = background != null ? background.getIntrinsicHeight() : 0;
final int availw = dw-bw;
final int availh = dh-bh;
int xPixels = availw < 0 ? (int)(availw*mXOffset+.5f) : (availw/2);
@@ -148,7 +149,9 @@ public class ImageWallpaper extends WallpaperService {
c.drawColor(0xff000000);
c.restore();
}
- background.draw(c);
+ if (background != null) {
+ background.draw(c);
+ }
}
sh.unlockCanvasAndPost(c);
}
@@ -156,7 +159,11 @@ public class ImageWallpaper extends WallpaperService {
void updateWallpaper() {
synchronized (mLock) {
- mBackground = mWallpaperManager.getFastDrawable();
+ try {
+ mBackground = mWallpaperManager.getFastDrawable();
+ } catch (RuntimeException e) {
+ Log.w("ImageWallpaper", "Unable to load wallpaper!", e);
+ }
}
}
}
diff --git a/core/java/com/android/internal/widget/ContactHeaderWidget.java b/core/java/com/android/internal/widget/ContactHeaderWidget.java
index d441155..c4f9988 100644
--- a/core/java/com/android/internal/widget/ContactHeaderWidget.java
+++ b/core/java/com/android/internal/widget/ContactHeaderWidget.java
@@ -120,6 +120,14 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
int CONTACT_STATUS_LABEL = 9;
}
+ private interface PhotoQuery {
+ String[] COLUMNS = new String[] {
+ Photo.PHOTO
+ };
+
+ int PHOTO = 0;
+ }
+
//Projection used for looking up contact id from phone number
protected static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
PhoneLookup._ID,
@@ -144,6 +152,7 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
private static final int TOKEN_CONTACT_INFO = 0;
private static final int TOKEN_PHONE_LOOKUP = 1;
private static final int TOKEN_EMAIL_LOOKUP = 2;
+ private static final int TOKEN_PHOTO_QUERY = 3;
public ContactHeaderWidget(Context context) {
this(context, null);
@@ -192,7 +201,7 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
mNoPhotoResource = R.drawable.ic_contact_picture_3;
}
- mQueryHandler = new QueryHandler(mContentResolver);
+ resetAsyncQueryHandler();
}
public void enableClickListeners() {
@@ -228,21 +237,60 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
try{
+ if (this != mQueryHandler) {
+ Log.d(TAG, "onQueryComplete: discard result, the query handler is reset!");
+ return;
+ }
+
switch (token) {
- case TOKEN_CONTACT_INFO: {
- bindContactInfo(cursor);
+ case TOKEN_PHOTO_QUERY: {
+ //Set the photo
+ Bitmap photoBitmap = null;
+ if (cursor != null && cursor.moveToFirst()
+ && !cursor.isNull(PhotoQuery.PHOTO)) {
+ byte[] photoData = cursor.getBlob(PhotoQuery.PHOTO);
+ photoBitmap = BitmapFactory.decodeByteArray(photoData, 0,
+ photoData.length, null);
+ }
+
+ if (photoBitmap == null) {
+ photoBitmap = loadPlaceholderPhoto(null);
+ }
+ mPhotoView.setImageBitmap(photoBitmap);
+ if (cookie != null && cookie instanceof Uri) {
+ mPhotoView.assignContactUri((Uri) cookie);
+ }
invalidate();
break;
}
+ case TOKEN_CONTACT_INFO: {
+ if (cursor != null && cursor.moveToFirst()) {
+ bindContactInfo(cursor);
+ Uri lookupUri = Contacts.getLookupUri(cursor.getLong(ContactQuery._ID),
+ cursor.getString(ContactQuery.LOOKUP_KEY));
+ startPhotoQuery(cursor.getLong(ContactQuery.PHOTO_ID),
+ lookupUri, false /* don't reset query handler */);
+ invalidate();
+ } else {
+ // shouldn't really happen
+ setDisplayName(null, null);
+ setSocialSnippet(null);
+ setPhoto(loadPlaceholderPhoto(null));
+ }
+ break;
+ }
case TOKEN_PHONE_LOOKUP: {
if (cursor != null && cursor.moveToFirst()) {
long contactId = cursor.getLong(PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX);
String lookupKey = cursor.getString(
PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
- bindFromContactUri(Contacts.getLookupUri(contactId, lookupKey));
+ bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
+ false /* don't reset query handler */);
} else {
String phoneNumber = (String) cookie;
setDisplayName(phoneNumber, null);
+ setSocialSnippet(null);
+ setPhoto(loadPlaceholderPhoto(null));
mPhotoView.assignContactFromPhone(phoneNumber, true);
}
break;
@@ -252,10 +300,13 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
long contactId = cursor.getLong(EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX);
String lookupKey = cursor.getString(
EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
- bindFromContactUri(Contacts.getLookupUri(contactId, lookupKey));
+ bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
+ false /* don't reset query handler */);
} else {
String emailAddress = (String) cookie;
setDisplayName(emailAddress, null);
+ setSocialSnippet(null);
+ setPhoto(loadPlaceholderPhoto(null));
mPhotoView.assignContactFromEmail(emailAddress, true);
}
break;
@@ -340,6 +391,7 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
public void setSocialSnippet(CharSequence snippet) {
if (snippet == null) {
mStatusView.setVisibility(View.GONE);
+ mStatusAttributionView.setVisibility(View.GONE);
} else {
mStatusView.setText(snippet);
mStatusView.setVisibility(View.VISIBLE);
@@ -360,24 +412,22 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
* Convenience method for binding all available data from an existing
* contact.
*
- * @param conatctUri a {Contacts.CONTENT_LOOKUP_URI} style URI.
+ * @param contactLookupUri a {Contacts.CONTENT_LOOKUP_URI} style URI.
*/
public void bindFromContactLookupUri(Uri contactLookupUri) {
- mContactUri = contactLookupUri;
- startContactQuery(contactLookupUri);
+ bindFromContactUriInternal(contactLookupUri, true /* reset query handler */);
}
/**
* Convenience method for binding all available data from an existing
* contact.
*
- * @param conatctUri a {Contacts.CONTENT_URI} style URI.
+ * @param contactUri a {Contacts.CONTENT_URI} style URI.
+ * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
*/
- public void bindFromContactUri(Uri contactUri) {
+ private void bindFromContactUriInternal(Uri contactUri, boolean resetQueryHandler) {
mContactUri = contactUri;
- long contactId = ContentUris.parseId(contactUri);
-
- startContactQuery(contactUri);
+ startContactQuery(contactUri, resetQueryHandler);
}
/**
@@ -389,6 +439,8 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
* address, one of them will be chosen to bind to.
*/
public void bindFromEmail(String emailAddress) {
+ resetAsyncQueryHandler();
+
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, emailAddress,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)),
EMAIL_LOOKUP_PROJECTION, null, null, null);
@@ -403,51 +455,83 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
* number, one of them will be chosen to bind to.
*/
public void bindFromPhoneNumber(String number) {
+ resetAsyncQueryHandler();
+
mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, number,
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
PHONE_LOOKUP_PROJECTION, null, null, null);
}
/**
- * Method to force this widget to forget everything it knows about the contact.
- * The widget isn't automatically updated or redrawn.
+ * startContactQuery
+ *
+ * internal method to query contact by Uri.
*
+ * @param contactUri the contact uri
+ * @param resetQueryHandler whether to use a new AsyncQueryHandler or not
+ */
+ private void startContactQuery(Uri contactUri, boolean resetQueryHandler) {
+ if (resetQueryHandler) {
+ resetAsyncQueryHandler();
+ }
+
+ mQueryHandler.startQuery(TOKEN_CONTACT_INFO, null, contactUri, ContactQuery.COLUMNS,
+ null, null, null);
+ }
+
+ /**
+ * startPhotoQuery
+ *
+ * internal method to query contact photo by photo id and uri.
+ *
+ * @param photoId the photo id.
+ * @param lookupKey the lookup uri.
+ * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
+ */
+ protected void startPhotoQuery(long photoId, Uri lookupKey, boolean resetQueryHandler) {
+ if (resetQueryHandler) {
+ resetAsyncQueryHandler();
+ }
+
+ mQueryHandler.startQuery(TOKEN_PHOTO_QUERY, lookupKey,
+ ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PhotoQuery.COLUMNS,
+ null, null, null);
+ }
+
+ /**
+ * Method to force this widget to forget everything it knows about the contact.
+ * We need to stop any existing async queries for phone, email, contact, and photos.
*/
public void wipeClean() {
- setPhoto(null);
+ resetAsyncQueryHandler();
+
+ setDisplayName(null, null);
+ setPhoto(loadPlaceholderPhoto(null));
+ setSocialSnippet(null);
+ setPresence(0);
mContactUri = null;
mExcludeMimes = null;
}
- private void startContactQuery(Uri contactUri) {
- mQueryHandler.startQuery(TOKEN_CONTACT_INFO, null, contactUri, ContactQuery.COLUMNS,
- null, null, null);
+
+ private void resetAsyncQueryHandler() {
+ // the api AsyncQueryHandler.cancelOperation() doesn't really work. Since we really
+ // need the old async queries to be cancelled, let's do it the hard way.
+ mQueryHandler = new QueryHandler(mContentResolver);
}
/**
* Bind the contact details provided by the given {@link Cursor}.
*/
protected void bindContactInfo(Cursor c) {
- if (c == null || !c.moveToFirst()) return;
-
// TODO: Bring back phonetic name
final String displayName = c.getString(ContactQuery.DISPLAY_NAME);
- final long contactId = c.getLong(ContactQuery._ID);
- final String lookupKey = c.getString(ContactQuery.LOOKUP_KEY);
final String phoneticName = null;
this.setDisplayName(displayName, null);
final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
mStarredView.setChecked(starred);
- //Set the photo
- Bitmap photoBitmap = loadContactPhoto(c.getLong(ContactQuery.PHOTO_ID), null);
- if (photoBitmap == null) {
- photoBitmap = loadPlaceholderPhoto(null);
- }
- mPhotoView.setImageBitmap(photoBitmap);
- mPhotoView.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
-
//Set the presence status
if (!c.isNull(ContactQuery.CONTACT_PRESENCE_STATUS)) {
int presence = c.getInt(ContactQuery.CONTACT_PRESENCE_STATUS);
@@ -554,30 +638,6 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
}
}
- private Bitmap loadContactPhoto(long photoId, BitmapFactory.Options options) {
- Cursor photoCursor = null;
- Bitmap photoBm = null;
-
- try {
- photoCursor = mContentResolver.query(
- ContentUris.withAppendedId(Data.CONTENT_URI, photoId),
- new String[] { Photo.PHOTO },
- null, null, null);
-
- if (photoCursor != null && photoCursor.moveToFirst() && !photoCursor.isNull(0)) {
- byte[] photoData = photoCursor.getBlob(0);
- photoBm = BitmapFactory.decodeByteArray(photoData, 0,
- photoData.length, options);
- }
- } finally {
- if (photoCursor != null) {
- photoCursor.close();
- }
- }
-
- return photoBm;
- }
-
private Bitmap loadPlaceholderPhoto(BitmapFactory.Options options) {
if (mNoPhotoResource == 0) {
return null;
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
new file mode 100644
index 0000000..fa47ff6
--- /dev/null
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import com.android.internal.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Typeface;
+import android.os.Handler;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+
+/**
+ * Displays the time
+ */
+public class DigitalClock extends LinearLayout {
+
+ private final static String M12 = "h:mm";
+ private final static String M24 = "kk:mm";
+
+ private Calendar mCalendar;
+ private String mFormat;
+ private TextView mTimeDisplay;
+ private AmPm mAmPm;
+ private ContentObserver mFormatChangeObserver;
+ private boolean mLive = true;
+ private boolean mAttached;
+
+ /* called by system on minute ticks */
+ private final Handler mHandler = new Handler();
+ private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mLive && intent.getAction().equals(
+ Intent.ACTION_TIMEZONE_CHANGED)) {
+ mCalendar = Calendar.getInstance();
+ }
+ // Post a runnable to avoid blocking the broadcast.
+ mHandler.post(new Runnable() {
+ public void run() {
+ updateTime();
+ }
+ });
+ }
+ };
+
+ static class AmPm {
+ private TextView mAmPm;
+ private String mAmString, mPmString;
+
+ AmPm(View parent, Typeface tf) {
+ mAmPm = (TextView) parent.findViewById(R.id.am_pm);
+ if (tf != null) {
+ mAmPm.setTypeface(tf);
+ }
+
+ String[] ampm = new DateFormatSymbols().getAmPmStrings();
+ mAmString = ampm[0];
+ mPmString = ampm[1];
+ }
+
+ void setShowAmPm(boolean show) {
+ mAmPm.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ void setIsMorning(boolean isMorning) {
+ mAmPm.setText(isMorning ? mAmString : mPmString);
+ }
+ }
+
+ private class FormatChangeObserver extends ContentObserver {
+ public FormatChangeObserver() {
+ super(new Handler());
+ }
+ @Override
+ public void onChange(boolean selfChange) {
+ setDateFormat();
+ updateTime();
+ }
+ }
+
+ public DigitalClock(Context context) {
+ this(context, null);
+ }
+
+ public DigitalClock(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mTimeDisplay = (TextView) findViewById(R.id.timeDisplay);
+ mTimeDisplay.setTypeface(Typeface.createFromFile("/system/fonts/Clockopia.ttf"));
+ mAmPm = new AmPm(this, Typeface.createFromFile("/system/fonts/DroidSans-Bold.ttf"));
+ mCalendar = Calendar.getInstance();
+
+ setDateFormat();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mAttached) return;
+ mAttached = true;
+
+ if (mLive) {
+ /* monitor time ticks, time changed, timezone */
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ mContext.registerReceiver(mIntentReceiver, filter);
+ }
+
+ /* monitor 12/24-hour display preference */
+ mFormatChangeObserver = new FormatChangeObserver();
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.CONTENT_URI, true, mFormatChangeObserver);
+
+ updateTime();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (!mAttached) return;
+ mAttached = false;
+
+ if (mLive) {
+ mContext.unregisterReceiver(mIntentReceiver);
+ }
+ mContext.getContentResolver().unregisterContentObserver(
+ mFormatChangeObserver);
+ }
+
+
+ void updateTime(Calendar c) {
+ mCalendar = c;
+ updateTime();
+ }
+
+ private void updateTime() {
+ if (mLive) {
+ mCalendar.setTimeInMillis(System.currentTimeMillis());
+ }
+
+ CharSequence newTime = DateFormat.format(mFormat, mCalendar);
+ mTimeDisplay.setText(newTime);
+ mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0);
+ }
+
+ private void setDateFormat() {
+ mFormat = android.text.format.DateFormat.is24HourFormat(getContext())
+ ? M24 : M12;
+ mAmPm.setShowAmPm(mFormat.equals(M12));
+ }
+
+ void setLive(boolean live) {
+ mLive = live;
+ }
+}
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
new file mode 100644
index 0000000..07955c4
--- /dev/null
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -0,0 +1,849 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.TranslateAnimation;
+import android.view.animation.Animation.AnimationListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.ImageView.ScaleType;
+import com.android.internal.R;
+
+/**
+ * A special widget containing two Sliders and a threshold for each. Moving either slider beyond
+ * the threshold will cause the registered OnTriggerListener.onTrigger() to be called with
+ * whichHandle being {@link OnTriggerListener#LEFT_HANDLE} or {@link OnTriggerListener#RIGHT_HANDLE}
+ * Equivalently, selecting a tab will result in a call to
+ * {@link OnTriggerListener#onGrabbedStateChange(View, int)} with one of these two states. Releasing
+ * the tab will result in whichHandle being {@link OnTriggerListener#NO_HANDLE}.
+ *
+ */
+public class SlidingTab extends ViewGroup {
+ private static final String LOG_TAG = "SlidingTab";
+ private static final boolean DBG = false;
+ private static final int HORIZONTAL = 0; // as defined in attrs.xml
+ private static final int VERTICAL = 1;
+
+ // TODO: Make these configurable
+ private static final float THRESHOLD = 2.0f / 3.0f;
+ private static final long VIBRATE_SHORT = 30;
+ private static final long VIBRATE_LONG = 40;
+ private static final int TRACKING_MARGIN = 50;
+ private static final int ANIM_DURATION = 250; // Time for most animations (in ms)
+ private static final int ANIM_TARGET_TIME = 500; // Time to show targets (in ms)
+ private boolean mHoldLeftOnTransition = true;
+ private boolean mHoldRightOnTransition = true;
+
+ private OnTriggerListener mOnTriggerListener;
+ private int mGrabbedState = OnTriggerListener.NO_HANDLE;
+ private boolean mTriggered = false;
+ private Vibrator mVibrator;
+ private float mDensity; // used to scale dimensions for bitmaps.
+
+ /**
+ * Either {@link #HORIZONTAL} or {@link #VERTICAL}.
+ */
+ private int mOrientation;
+
+ private Slider mLeftSlider;
+ private Slider mRightSlider;
+ private Slider mCurrentSlider;
+ private boolean mTracking;
+ private float mThreshold;
+ private Slider mOtherSlider;
+ private boolean mAnimating;
+ private Rect mTmpRect;
+
+ /**
+ * Listener used to reset the view when the current animation completes.
+ */
+ private final AnimationListener mAnimationDoneListener = new AnimationListener() {
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+
+ public void onAnimationEnd(Animation animation) {
+ onAnimationDone();
+ }
+ };
+
+ /**
+ * Interface definition for a callback to be invoked when a tab is triggered
+ * by moving it beyond a threshold.
+ */
+ public interface OnTriggerListener {
+ /**
+ * The interface was triggered because the user let go of the handle without reaching the
+ * threshold.
+ */
+ public static final int NO_HANDLE = 0;
+
+ /**
+ * The interface was triggered because the user grabbed the left handle and moved it past
+ * the threshold.
+ */
+ public static final int LEFT_HANDLE = 1;
+
+ /**
+ * The interface was triggered because the user grabbed the right handle and moved it past
+ * the threshold.
+ */
+ public static final int RIGHT_HANDLE = 2;
+
+ /**
+ * Called when the user moves a handle beyond the threshold.
+ *
+ * @param v The view that was triggered.
+ * @param whichHandle Which "dial handle" the user grabbed,
+ * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}.
+ */
+ void onTrigger(View v, int whichHandle);
+
+ /**
+ * Called when the "grabbed state" changes (i.e. when the user either grabs or releases
+ * one of the handles.)
+ *
+ * @param v the view that was triggered
+ * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #LEFT_HANDLE},
+ * or {@link #RIGHT_HANDLE}.
+ */
+ void onGrabbedStateChange(View v, int grabbedState);
+ }
+
+ /**
+ * Simple container class for all things pertinent to a slider.
+ * A slider consists of 3 Views:
+ *
+ * {@link #tab} is the tab shown on the screen in the default state.
+ * {@link #text} is the view revealed as the user slides the tab out.
+ * {@link #target} is the target the user must drag the slider past to trigger the slider.
+ *
+ */
+ private static class Slider {
+ /**
+ * Tab alignment - determines which side the tab should be drawn on
+ */
+ public static final int ALIGN_LEFT = 0;
+ public static final int ALIGN_RIGHT = 1;
+ public static final int ALIGN_TOP = 2;
+ public static final int ALIGN_BOTTOM = 3;
+ public static final int ALIGN_UNKNOWN = 4;
+
+ /**
+ * States for the view.
+ */
+ private static final int STATE_NORMAL = 0;
+ private static final int STATE_PRESSED = 1;
+ private static final int STATE_ACTIVE = 2;
+
+ private final ImageView tab;
+ private final TextView text;
+ private final ImageView target;
+ private int currentState = STATE_NORMAL;
+ private int alignment = ALIGN_UNKNOWN;
+ private int alignment_value;
+
+ /**
+ * Constructor
+ *
+ * @param parent the container view of this one
+ * @param tabId drawable for the tab
+ * @param barId drawable for the bar
+ * @param targetId drawable for the target
+ */
+ Slider(ViewGroup parent, int tabId, int barId, int targetId) {
+ // Create tab
+ tab = new ImageView(parent.getContext());
+ tab.setBackgroundResource(tabId);
+ tab.setScaleType(ScaleType.CENTER);
+ tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+
+ // Create hint TextView
+ text = new TextView(parent.getContext());
+ text.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.FILL_PARENT));
+ text.setBackgroundResource(barId);
+ text.setTextAppearance(parent.getContext(), R.style.TextAppearance_SlidingTabNormal);
+ // hint.setSingleLine(); // Hmm.. this causes the text to disappear off-screen
+
+ // Create target
+ target = new ImageView(parent.getContext());
+ target.setImageResource(targetId);
+ target.setScaleType(ScaleType.CENTER);
+ target.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ target.setVisibility(View.INVISIBLE);
+
+ parent.addView(target); // this needs to be first - relies on painter's algorithm
+ parent.addView(tab);
+ parent.addView(text);
+ }
+
+ void setIcon(int iconId) {
+ tab.setImageResource(iconId);
+ }
+
+ void setTabBackgroundResource(int tabId) {
+ tab.setBackgroundResource(tabId);
+ }
+
+ void setBarBackgroundResource(int barId) {
+ text.setBackgroundResource(barId);
+ }
+
+ void setHintText(int resId) {
+ text.setText(resId);
+ }
+
+ void hide() {
+ boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
+ int dx = horiz ? (alignment == ALIGN_LEFT ? alignment_value - tab.getRight()
+ : alignment_value - tab.getLeft()) : 0;
+ int dy = horiz ? 0 : (alignment == ALIGN_TOP ? alignment_value - tab.getBottom()
+ : alignment_value - tab.getTop());
+
+ Animation trans = new TranslateAnimation(0, dx, 0, dy);
+ trans.setDuration(ANIM_DURATION);
+ trans.setFillAfter(true);
+ tab.startAnimation(trans);
+ text.startAnimation(trans);
+ target.setVisibility(View.INVISIBLE);
+ }
+
+ void show(boolean animate) {
+ text.setVisibility(View.VISIBLE);
+ tab.setVisibility(View.VISIBLE);
+ //target.setVisibility(View.INVISIBLE);
+ if (animate) {
+ boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
+ int dx = horiz ? (alignment == ALIGN_LEFT ? tab.getWidth() : -tab.getWidth()) : 0;
+ int dy = horiz ? 0: (alignment == ALIGN_TOP ? tab.getHeight() : -tab.getHeight());
+
+ Animation trans = new TranslateAnimation(-dx, 0, -dy, 0);
+ trans.setDuration(ANIM_DURATION);
+ tab.startAnimation(trans);
+ text.startAnimation(trans);
+ }
+ }
+
+ void setState(int state) {
+ text.setPressed(state == STATE_PRESSED);
+ tab.setPressed(state == STATE_PRESSED);
+ if (state == STATE_ACTIVE) {
+ final int[] activeState = new int[] {com.android.internal.R.attr.state_active};
+ if (text.getBackground().isStateful()) {
+ text.getBackground().setState(activeState);
+ }
+ if (tab.getBackground().isStateful()) {
+ tab.getBackground().setState(activeState);
+ }
+ text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabActive);
+ } else {
+ text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
+ }
+ currentState = state;
+ }
+
+ void showTarget() {
+ AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
+ alphaAnim.setDuration(ANIM_TARGET_TIME);
+ target.startAnimation(alphaAnim);
+ target.setVisibility(View.VISIBLE);
+ }
+
+ void reset(boolean animate) {
+ setState(STATE_NORMAL);
+ text.setVisibility(View.VISIBLE);
+ text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
+ tab.setVisibility(View.VISIBLE);
+ target.setVisibility(View.INVISIBLE);
+ final boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
+ int dx = horiz ? (alignment == ALIGN_LEFT ? alignment_value - tab.getLeft()
+ : alignment_value - tab.getRight()) : 0;
+ int dy = horiz ? 0 : (alignment == ALIGN_TOP ? alignment_value - tab.getTop()
+ : alignment_value - tab.getBottom());
+ if (animate) {
+ TranslateAnimation trans = new TranslateAnimation(0, dx, 0, dy);
+ trans.setDuration(ANIM_DURATION);
+ trans.setFillAfter(false);
+ text.startAnimation(trans);
+ tab.startAnimation(trans);
+ } else {
+ if (horiz) {
+ text.offsetLeftAndRight(dx);
+ tab.offsetLeftAndRight(dx);
+ } else {
+ text.offsetTopAndBottom(dy);
+ tab.offsetTopAndBottom(dy);
+ }
+ text.clearAnimation();
+ tab.clearAnimation();
+ target.clearAnimation();
+ }
+ }
+
+ void setTarget(int targetId) {
+ target.setImageResource(targetId);
+ }
+
+ /**
+ * Layout the given widgets within the parent.
+ *
+ * @param l the parent's left border
+ * @param t the parent's top border
+ * @param r the parent's right border
+ * @param b the parent's bottom border
+ * @param alignment which side to align the widget to
+ */
+ void layout(int l, int t, int r, int b, int alignment) {
+ this.alignment = alignment;
+ final Drawable tabBackground = tab.getBackground();
+ final int handleWidth = tabBackground.getIntrinsicWidth();
+ final int handleHeight = tabBackground.getIntrinsicHeight();
+ final Drawable targetDrawable = target.getDrawable();
+ final int targetWidth = targetDrawable.getIntrinsicWidth();
+ final int targetHeight = targetDrawable.getIntrinsicHeight();
+ final int parentWidth = r - l;
+ final int parentHeight = b - t;
+
+ final int leftTarget = (int) (THRESHOLD * parentWidth) - targetWidth + handleWidth / 2;
+ final int rightTarget = (int) ((1.0f - THRESHOLD) * parentWidth) - handleWidth / 2;
+ final int left = (parentWidth - handleWidth) / 2;
+ final int right = left + handleWidth;
+
+ if (alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT) {
+ // horizontal
+ final int targetTop = (parentHeight - targetHeight) / 2;
+ final int targetBottom = targetTop + targetHeight;
+ final int top = (parentHeight - handleHeight) / 2;
+ final int bottom = (parentHeight + handleHeight) / 2;
+ if (alignment == ALIGN_LEFT) {
+ tab.layout(0, top, handleWidth, bottom);
+ text.layout(0 - parentWidth, top, 0, bottom);
+ text.setGravity(Gravity.RIGHT);
+ target.layout(leftTarget, targetTop, leftTarget + targetWidth, targetBottom);
+ alignment_value = l;
+ } else {
+ tab.layout(parentWidth - handleWidth, top, parentWidth, bottom);
+ text.layout(parentWidth, top, parentWidth + parentWidth, bottom);
+ target.layout(rightTarget, targetTop, rightTarget + targetWidth, targetBottom);
+ text.setGravity(Gravity.TOP);
+ alignment_value = r;
+ }
+ } else {
+ // vertical
+ final int targetLeft = (parentWidth - targetWidth) / 2;
+ final int targetRight = (parentWidth + targetWidth) / 2;
+ final int top = (int) (THRESHOLD * parentHeight) + handleHeight / 2 - targetHeight;
+ final int bottom = (int) ((1.0f - THRESHOLD) * parentHeight) - handleHeight / 2;
+ if (alignment == ALIGN_TOP) {
+ tab.layout(left, 0, right, handleHeight);
+ text.layout(left, 0 - parentHeight, right, 0);
+ target.layout(targetLeft, top, targetRight, top + targetHeight);
+ alignment_value = t;
+ } else {
+ tab.layout(left, parentHeight - handleHeight, right, parentHeight);
+ text.layout(left, parentHeight, right, parentHeight + parentHeight);
+ target.layout(targetLeft, bottom, targetRight, bottom + targetHeight);
+ alignment_value = b;
+ }
+ }
+ }
+
+ public void updateDrawableStates() {
+ setState(currentState);
+ }
+
+ /**
+ * Ensure all the dependent widgets are measured.
+ */
+ public void measure() {
+ tab.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+ text.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+ }
+
+ /**
+ * Get the measured tab width. Must be called after {@link Slider#measure()}.
+ * @return
+ */
+ public int getTabWidth() {
+ return tab.getMeasuredWidth();
+ }
+
+ /**
+ * Get the measured tab width. Must be called after {@link Slider#measure()}.
+ * @return
+ */
+ public int getTabHeight() {
+ return tab.getMeasuredHeight();
+ }
+
+ /**
+ * Start animating the slider. Note we need two animations since an Animator
+ * keeps internal state of the invalidation region which is just the view being animated.
+ *
+ * @param anim1
+ * @param anim2
+ */
+ public void startAnimation(Animation anim1, Animation anim2) {
+ tab.startAnimation(anim1);
+ text.startAnimation(anim2);
+ }
+
+ public void hideTarget() {
+ target.clearAnimation();
+ target.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public SlidingTab(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor used when this widget is created from a layout file.
+ */
+ public SlidingTab(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // Allocate a temporary once that can be used everywhere.
+ mTmpRect = new Rect();
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingTab);
+ mOrientation = a.getInt(R.styleable.SlidingTab_orientation, HORIZONTAL);
+ a.recycle();
+
+ Resources r = getResources();
+ mDensity = r.getDisplayMetrics().density;
+ if (DBG) log("- Density: " + mDensity);
+
+ mLeftSlider = new Slider(this,
+ R.drawable.jog_tab_left_generic,
+ R.drawable.jog_tab_bar_left_generic,
+ R.drawable.jog_tab_target_gray);
+ mRightSlider = new Slider(this,
+ R.drawable.jog_tab_right_generic,
+ R.drawable.jog_tab_bar_right_generic,
+ R.drawable.jog_tab_target_gray);
+
+ // setBackgroundColor(0x80808080);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ throw new RuntimeException(LOG_TAG + " cannot have UNSPECIFIED dimensions");
+ }
+
+ mLeftSlider.measure();
+ mRightSlider.measure();
+ final int leftTabWidth = mLeftSlider.getTabWidth();
+ final int rightTabWidth = mRightSlider.getTabWidth();
+ final int leftTabHeight = mLeftSlider.getTabHeight();
+ final int rightTabHeight = mRightSlider.getTabHeight();
+ final int width;
+ final int height;
+ if (isHorizontal()) {
+ width = Math.max(widthSpecSize, leftTabWidth + rightTabWidth);
+ height = Math.max(leftTabHeight, rightTabHeight);
+ } else {
+ width = Math.max(leftTabWidth, rightTabHeight);
+ height = Math.max(heightSpecSize, leftTabHeight + rightTabHeight);
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (mAnimating) {
+ return false;
+ }
+
+ View leftHandle = mLeftSlider.tab;
+ leftHandle.getHitRect(mTmpRect);
+ boolean leftHit = mTmpRect.contains((int) x, (int) y);
+
+ View rightHandle = mRightSlider.tab;
+ rightHandle.getHitRect(mTmpRect);
+ boolean rightHit = mTmpRect.contains((int)x, (int) y);
+
+ if (!mTracking && !(leftHit || rightHit)) {
+ return false;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mTracking = true;
+ mTriggered = false;
+ vibrate(VIBRATE_SHORT);
+ if (leftHit) {
+ mCurrentSlider = mLeftSlider;
+ mOtherSlider = mRightSlider;
+ mThreshold = isHorizontal() ? THRESHOLD : 1.0f - THRESHOLD;
+ setGrabbedState(OnTriggerListener.LEFT_HANDLE);
+ } else {
+ mCurrentSlider = mRightSlider;
+ mOtherSlider = mLeftSlider;
+ mThreshold = isHorizontal() ? 1.0f - THRESHOLD : THRESHOLD;
+ setGrabbedState(OnTriggerListener.RIGHT_HANDLE);
+ }
+ mCurrentSlider.setState(Slider.STATE_PRESSED);
+ mCurrentSlider.showTarget();
+ mOtherSlider.hide();
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Reset the tabs to their original state and stop any existing animation.
+ * Animate them back into place if animate is true.
+ *
+ * @param animate
+ */
+ public void reset(boolean animate) {
+ mLeftSlider.reset(animate);
+ mRightSlider.reset(animate);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ // Clear animations so sliders don't continue to animate when we show the widget again.
+ if (visibility != getVisibility() && visibility == View.INVISIBLE) {
+ reset(false);
+ }
+ super.setVisibility(visibility);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mTracking) {
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ if (withinView(x, y, this) ) {
+ moveHandle(x, y);
+ float position = isHorizontal() ? x : y;
+ float target = mThreshold * (isHorizontal() ? getWidth() : getHeight());
+ boolean thresholdReached;
+ if (isHorizontal()) {
+ thresholdReached = mCurrentSlider == mLeftSlider ?
+ position > target : position < target;
+ } else {
+ thresholdReached = mCurrentSlider == mLeftSlider ?
+ position < target : position > target;
+ }
+ if (!mTriggered && thresholdReached) {
+ mTriggered = true;
+ mTracking = false;
+ mCurrentSlider.setState(Slider.STATE_ACTIVE);
+ boolean isLeft = mCurrentSlider == mLeftSlider;
+ dispatchTriggerEvent(isLeft ?
+ OnTriggerListener.LEFT_HANDLE : OnTriggerListener.RIGHT_HANDLE);
+
+ startAnimating(isLeft ? mHoldLeftOnTransition : mHoldRightOnTransition);
+ setGrabbedState(OnTriggerListener.NO_HANDLE);
+ }
+ break;
+ }
+ // Intentionally fall through - we're outside tracking rectangle
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mTracking = false;
+ mTriggered = false;
+ mOtherSlider.show(true);
+ mCurrentSlider.reset(false);
+ mCurrentSlider.hideTarget();
+ mCurrentSlider = null;
+ mOtherSlider = null;
+ setGrabbedState(OnTriggerListener.NO_HANDLE);
+ break;
+ }
+ }
+
+ return mTracking || super.onTouchEvent(event);
+ }
+
+ void startAnimating(final boolean holdAfter) {
+ mAnimating = true;
+ final Animation trans1;
+ final Animation trans2;
+ final Slider slider = mCurrentSlider;
+ final Slider other = mOtherSlider;
+ final int dx;
+ final int dy;
+ if (isHorizontal()) {
+ int right = slider.tab.getRight();
+ int width = slider.tab.getWidth();
+ int left = slider.tab.getLeft();
+ int viewWidth = getWidth();
+ int holdOffset = holdAfter ? 0 : width; // how much of tab to show at the end of anim
+ dx = slider == mRightSlider ? - (right + viewWidth - holdOffset)
+ : (viewWidth - left) + viewWidth - holdOffset;
+ dy = 0;
+ } else {
+ int top = slider.tab.getTop();
+ int bottom = slider.tab.getBottom();
+ int height = slider.tab.getHeight();
+ int viewHeight = getHeight();
+ int holdOffset = holdAfter ? 0 : height; // how much of tab to show at end of anim
+ dx = 0;
+ dy = slider == mRightSlider ? (top + viewHeight - holdOffset)
+ : - ((viewHeight - bottom) + viewHeight - holdOffset);
+ }
+ trans1 = new TranslateAnimation(0, dx, 0, dy);
+ trans1.setDuration(ANIM_DURATION);
+ trans1.setInterpolator(new LinearInterpolator());
+ trans1.setFillAfter(true);
+ trans2 = new TranslateAnimation(0, dx, 0, dy);
+ trans2.setDuration(ANIM_DURATION);
+ trans2.setInterpolator(new LinearInterpolator());
+ trans2.setFillAfter(true);
+
+ trans1.setAnimationListener(new AnimationListener() {
+ public void onAnimationEnd(Animation animation) {
+ Animation anim;
+ if (holdAfter) {
+ anim = new TranslateAnimation(dx, dx, dy, dy);
+ anim.setDuration(1000); // plenty of time for transitions
+ mAnimating = false;
+ } else {
+ anim = new AlphaAnimation(0.5f, 1.0f);
+ anim.setDuration(ANIM_DURATION);
+ resetView();
+ }
+ anim.setAnimationListener(mAnimationDoneListener);
+
+ /* Animation can be the same for these since the animation just holds */
+ mLeftSlider.startAnimation(anim, anim);
+ mRightSlider.startAnimation(anim, anim);
+ }
+
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ });
+
+ slider.hideTarget();
+ slider.startAnimation(trans1, trans2);
+ }
+
+ private void onAnimationDone() {
+ resetView();
+ mAnimating = false;
+ }
+
+ private boolean withinView(final float x, final float y, final View view) {
+ return isHorizontal() && y > - TRACKING_MARGIN && y < TRACKING_MARGIN + view.getHeight()
+ || !isHorizontal() && x > -TRACKING_MARGIN && x < TRACKING_MARGIN + view.getWidth();
+ }
+
+ private boolean isHorizontal() {
+ return mOrientation == HORIZONTAL;
+ }
+
+ private void resetView() {
+ mLeftSlider.reset(false);
+ mRightSlider.reset(false);
+ // onLayout(true, getLeft(), getTop(), getLeft() + getWidth(), getTop() + getHeight());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (!changed) return;
+
+ // Center the widgets in the view
+ mLeftSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_LEFT : Slider.ALIGN_BOTTOM);
+ mRightSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_RIGHT : Slider.ALIGN_TOP);
+ }
+
+ private void moveHandle(float x, float y) {
+ final View handle = mCurrentSlider.tab;
+ final View content = mCurrentSlider.text;
+ if (isHorizontal()) {
+ int deltaX = (int) x - handle.getLeft() - (handle.getWidth() / 2);
+ handle.offsetLeftAndRight(deltaX);
+ content.offsetLeftAndRight(deltaX);
+ } else {
+ int deltaY = (int) y - handle.getTop() - (handle.getHeight() / 2);
+ handle.offsetTopAndBottom(deltaY);
+ content.offsetTopAndBottom(deltaY);
+ }
+ invalidate(); // TODO: be more conservative about what we're invalidating
+ }
+
+ /**
+ * Sets the left handle icon to a given resource.
+ *
+ * The resource should refer to a Drawable object, or use 0 to remove
+ * the icon.
+ *
+ * @param iconId the resource ID of the icon drawable
+ * @param targetId the resource of the target drawable
+ * @param barId the resource of the bar drawable (stateful)
+ * @param tabId the resource of the
+ */
+ public void setLeftTabResources(int iconId, int targetId, int barId, int tabId) {
+ mLeftSlider.setIcon(iconId);
+ mLeftSlider.setTarget(targetId);
+ mLeftSlider.setBarBackgroundResource(barId);
+ mLeftSlider.setTabBackgroundResource(tabId);
+ mLeftSlider.updateDrawableStates();
+ }
+
+ /**
+ * Sets the left handle hint text to a given resource string.
+ *
+ * @param resId
+ */
+ public void setLeftHintText(int resId) {
+ if (isHorizontal()) {
+ mLeftSlider.setHintText(resId);
+ }
+ }
+
+ /**
+ * Sets the right handle icon to a given resource.
+ *
+ * The resource should refer to a Drawable object, or use 0 to remove
+ * the icon.
+ *
+ * @param iconId the resource ID of the icon drawable
+ * @param targetId the resource of the target drawable
+ * @param barId the resource of the bar drawable (stateful)
+ * @param tabId the resource of the
+ */
+ public void setRightTabResources(int iconId, int targetId, int barId, int tabId) {
+ mRightSlider.setIcon(iconId);
+ mRightSlider.setTarget(targetId);
+ mRightSlider.setBarBackgroundResource(barId);
+ mRightSlider.setTabBackgroundResource(tabId);
+ mRightSlider.updateDrawableStates();
+ }
+
+ /**
+ * Sets the left handle hint text to a given resource string.
+ *
+ * @param resId
+ */
+ public void setRightHintText(int resId) {
+ if (isHorizontal()) {
+ mRightSlider.setHintText(resId);
+ }
+ }
+
+ public void setHoldAfterTrigger(boolean holdLeft, boolean holdRight) {
+ mHoldLeftOnTransition = holdLeft;
+ mHoldRightOnTransition = holdRight;
+ }
+
+ /**
+ * Triggers haptic feedback.
+ */
+ private synchronized void vibrate(long duration) {
+ if (mVibrator == null) {
+ mVibrator = (android.os.Vibrator)
+ getContext().getSystemService(Context.VIBRATOR_SERVICE);
+ }
+ mVibrator.vibrate(duration);
+ }
+
+ /**
+ * Registers a callback to be invoked when the user triggers an event.
+ *
+ * @param listener the OnDialTriggerListener to attach to this view
+ */
+ public void setOnTriggerListener(OnTriggerListener listener) {
+ mOnTriggerListener = listener;
+ }
+
+ /**
+ * Dispatches a trigger event to listener. Ignored if a listener is not set.
+ * @param whichHandle the handle that triggered the event.
+ */
+ private void dispatchTriggerEvent(int whichHandle) {
+ vibrate(VIBRATE_LONG);
+ if (mOnTriggerListener != null) {
+ mOnTriggerListener.onTrigger(this, whichHandle);
+ }
+ }
+
+ /**
+ * Sets the current grabbed state, and dispatches a grabbed state change
+ * event to our listener.
+ */
+ private void setGrabbedState(int newState) {
+ if (newState != mGrabbedState) {
+ mGrabbedState = newState;
+ if (mOnTriggerListener != null) {
+ mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
+ }
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, msg);
+ }
+}