summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt18
-rw-r--r--core/java/android/app/DownloadManager.java60
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java53
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/app/backup/BackupAgent.java28
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java15
-rw-r--r--core/java/android/net/TrafficStats.java133
-rw-r--r--core/java/android/os/IUserManager.aidl1
-rw-r--r--core/java/android/os/SystemClock.java2
-rw-r--r--core/java/android/os/UserManager.java92
-rw-r--r--core/java/android/text/format/DateUtils.java119
-rw-r--r--core/java/android/text/format/Formatter.java14
-rw-r--r--core/java/android/view/Overlay.java21
-rw-r--r--core/java/android/view/View.java4
-rw-r--r--core/java/android/view/ViewGroup.java53
-rw-r--r--core/java/android/view/ViewOverlay.java49
-rw-r--r--core/java/android/view/ViewRootImpl.java36
-rw-r--r--core/java/android/widget/AppSecurityPermissions.java113
-rw-r--r--core/java/android/widget/ExpandableListView.java193
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java5
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java15
-rw-r--r--core/jni/android_ddm_DdmHandleNativeHeap.cpp128
-rw-r--r--core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_dark.9.pngbin0 -> 136 bytes
-rw-r--r--core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_light.9.pngbin0 -> 137 bytes
-rw-r--r--core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_dark.9.pngbin0 -> 147 bytes
-rw-r--r--core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_light.9.pngbin0 -> 147 bytes
-rw-r--r--core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_dark.9.pngbin0 -> 147 bytes
-rw-r--r--core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_light.9.pngbin0 -> 149 bytes
-rw-r--r--core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_dark.9.pngbin0 -> 134 bytes
-rw-r--r--core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_light.9.pngbin0 -> 136 bytes
-rw-r--r--core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_dark.9.pngbin0 -> 143 bytes
-rw-r--r--core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_light.9.pngbin0 -> 145 bytes
-rw-r--r--core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_dark.9.pngbin0 -> 143 bytes
-rw-r--r--core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_light.9.pngbin0 -> 146 bytes
-rw-r--r--core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_dark.9.pngbin0 -> 144 bytes
-rw-r--r--core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_light.9.pngbin0 -> 145 bytes
-rw-r--r--core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_dark.9.pngbin0 -> 157 bytes
-rw-r--r--core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_light.9.pngbin0 -> 156 bytes
-rw-r--r--core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_dark.9.pngbin0 -> 157 bytes
-rw-r--r--core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_light.9.pngbin0 -> 156 bytes
-rw-r--r--core/res/res/values-in/strings.xml2
-rw-r--r--core/res/res/values-sw/strings.xml2
-rw-r--r--core/res/res/values/attrs.xml10
-rw-r--r--core/res/res/values/public.xml5
-rw-r--r--core/res/res/values/strings.xml3
-rw-r--r--docs/html/guide/topics/providers/calendar-provider.jd2
-rw-r--r--docs/html/training/contacts-provider/ContactsList.zipbin0 -> 483676 bytes
-rw-r--r--docs/html/training/contacts-provider/display-contact-badge.jd635
-rw-r--r--docs/html/training/contacts-provider/index.jd97
-rw-r--r--docs/html/training/contacts-provider/modify-data.jd305
-rw-r--r--docs/html/training/contacts-provider/retrieve-details.jd378
-rw-r--r--docs/html/training/contacts-provider/retrieve-names.jd815
-rw-r--r--docs/html/training/training_toc.cs34
-rw-r--r--graphics/java/android/renderscript/Type.java10
-rw-r--r--media/java/android/media/AudioService.java356
-rw-r--r--media/jni/android_media_MediaCrypto.cpp2
-rw-r--r--services/input/InputDispatcher.h2
-rw-r--r--services/java/com/android/server/BackupManagerService.java12
-rw-r--r--services/java/com/android/server/DevicePolicyManagerService.java177
-rw-r--r--services/java/com/android/server/MountService.java2
-rw-r--r--services/java/com/android/server/accounts/AccountManagerService.java8
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java2
-rw-r--r--services/java/com/android/server/pm/Installer.java4
-rw-r--r--services/java/com/android/server/pm/PackageManagerService.java97
-rw-r--r--services/java/com/android/server/pm/SELinuxMMAC.java272
-rw-r--r--services/java/com/android/server/pm/UserManagerService.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java14
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java6
68 files changed, 3753 insertions, 697 deletions
diff --git a/api/current.txt b/api/current.txt
index 2e28d7b..cfaa1bb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -339,8 +339,10 @@ package android {
field public static final int checkedTextViewStyle = 16843720; // 0x10103c8
field public static final int childDivider = 16843025; // 0x1010111
field public static final int childIndicator = 16843020; // 0x101010c
+ field public static final int childIndicatorEnd = 16843732; // 0x10103d4
field public static final int childIndicatorLeft = 16843023; // 0x101010f
field public static final int childIndicatorRight = 16843024; // 0x1010110
+ field public static final int childIndicatorStart = 16843731; // 0x10103d3
field public static final int choiceMode = 16843051; // 0x101012b
field public static final int clearTaskOnLaunch = 16842773; // 0x1010015
field public static final int clickable = 16842981; // 0x10100e5
@@ -574,8 +576,10 @@ package android {
field public static final int indeterminateDuration = 16843069; // 0x101013d
field public static final int indeterminateOnly = 16843066; // 0x101013a
field public static final int indeterminateProgressStyle = 16843544; // 0x1010318
+ field public static final int indicatorEnd = 16843730; // 0x10103d2
field public static final int indicatorLeft = 16843021; // 0x101010d
field public static final int indicatorRight = 16843022; // 0x101010e
+ field public static final int indicatorStart = 16843729; // 0x10103d1
field public static final int inflatedId = 16842995; // 0x10100f3
field public static final int initOrder = 16842778; // 0x101001a
field public static final int initialKeyguardLayout = 16843714; // 0x10103c2
@@ -4333,6 +4337,7 @@ package android.app.admin {
method public boolean hasGrantedPolicy(android.content.ComponentName, int);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
+ method public boolean isDeviceOwner(java.lang.String);
method public void lockNow();
method public void removeActiveAdmin(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
@@ -16784,9 +16789,20 @@ package android.os {
method public int getUserCount();
method public android.os.UserHandle getUserForSerialNumber(long);
method public java.lang.String getUserName();
+ method public android.os.Bundle getUserRestrictions();
+ method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean isUserAGoat();
+ method public boolean isUserRestricted();
method public boolean isUserRunning(android.os.UserHandle);
method public boolean isUserRunningOrStopping(android.os.UserHandle);
+ method public void setUserRestriction(java.lang.String, boolean);
+ method public void setUserRestrictions(android.os.Bundle);
+ method public void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
+ field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi";
+ field public static final java.lang.String DISALLOW_INSTALL_APPS = "no_install_apps";
+ field public static final java.lang.String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
+ field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
+ field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
}
public abstract class Vibrator {
@@ -28616,8 +28632,10 @@ package android.widget {
method public void setChildDivider(android.graphics.drawable.Drawable);
method public void setChildIndicator(android.graphics.drawable.Drawable);
method public void setChildIndicatorBounds(int, int);
+ method public void setChildIndicatorBoundsRelative(int, int);
method public void setGroupIndicator(android.graphics.drawable.Drawable);
method public void setIndicatorBounds(int, int);
+ method public void setIndicatorBoundsRelative(int, int);
method public void setOnChildClickListener(android.widget.ExpandableListView.OnChildClickListener);
method public void setOnGroupClickListener(android.widget.ExpandableListView.OnGroupClickListener);
method public void setOnGroupCollapseListener(android.widget.ExpandableListView.OnGroupCollapseListener);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 26dc60d..165c3db 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -461,39 +461,63 @@ public class DownloadManager {
}
/**
- * Set the local destination for the downloaded file to a path within the application's
- * external files directory (as returned by {@link Context#getExternalFilesDir(String)}.
+ * Set the local destination for the downloaded file to a path within
+ * the application's external files directory (as returned by
+ * {@link Context#getExternalFilesDir(String)}.
* <p>
- * The downloaded file is not scanned by MediaScanner.
- * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
+ * The downloaded file is not scanned by MediaScanner. But it can be
+ * made scannable by calling {@link #allowScanningByMediaScanner()}.
*
- * @param context the {@link Context} to use in determining the external files directory
- * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)}
- * @param subPath the path within the external directory, including the destination filename
+ * @param context the {@link Context} to use in determining the external
+ * files directory
+ * @param dirType the directory type to pass to
+ * {@link Context#getExternalFilesDir(String)}
+ * @param subPath the path within the external directory, including the
+ * destination filename
* @return this object
+ * @throws IllegalStateException If the external storage directory
+ * cannot be found or created.
*/
public Request setDestinationInExternalFilesDir(Context context, String dirType,
String subPath) {
- setDestinationFromBase(context.getExternalFilesDir(dirType), subPath);
+ final File file = context.getExternalFilesDir(dirType);
+ if (file == null) {
+ throw new IllegalStateException("Failed to get external storage files directory");
+ } else if (file.exists()) {
+ if (!file.isDirectory()) {
+ throw new IllegalStateException(file.getAbsolutePath() +
+ " already exists and is not a directory");
+ }
+ } else {
+ if (!file.mkdirs()) {
+ throw new IllegalStateException("Unable to create directory: "+
+ file.getAbsolutePath());
+ }
+ }
+ setDestinationFromBase(file, subPath);
return this;
}
/**
- * Set the local destination for the downloaded file to a path within the public external
- * storage directory (as returned by
- * {@link Environment#getExternalStoragePublicDirectory(String)}.
- *<p>
- * The downloaded file is not scanned by MediaScanner.
- * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
+ * Set the local destination for the downloaded file to a path within
+ * the public external storage directory (as returned by
+ * {@link Environment#getExternalStoragePublicDirectory(String)}).
+ * <p>
+ * The downloaded file is not scanned by MediaScanner. But it can be
+ * made scannable by calling {@link #allowScanningByMediaScanner()}.
*
- * @param dirType the directory type to pass to
- * {@link Environment#getExternalStoragePublicDirectory(String)}
- * @param subPath the path within the external directory, including the destination filename
+ * @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)}
+ * @param subPath the path within the external directory, including the
+ * destination filename
* @return this object
+ * @throws IllegalStateException If the external storage directory
+ * cannot be found or created.
*/
public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
File file = Environment.getExternalStoragePublicDirectory(dirType);
- if (file.exists()) {
+ if (file == null) {
+ throw new IllegalStateException("Failed to get external storage public directory");
+ } else if (file.exists()) {
if (!file.isDirectory()) {
throw new IllegalStateException(file.getAbsolutePath() +
" already exists and is not a directory");
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4c0eba0..8284b2c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1513,4 +1513,57 @@ public class DevicePolicyManager {
}
}
}
+
+ /**
+ * @hide
+ * Sets the given package as the device owner. The package must already be installed and there
+ * shouldn't be an existing device owner registered, for this call to succeed. Also, this
+ * method must be called before the device is provisioned.
+ * @param packageName the package name of the application to be registered as the device owner.
+ * @return whether the package was successfully registered as the device owner.
+ * @throws IllegalArgumentException if the package name is null or invalid
+ * @throws IllegalStateException if a device owner is already registered or the device has
+ * already been provisioned.
+ */
+ public boolean setDeviceOwner(String packageName) throws IllegalArgumentException,
+ IllegalStateException {
+ if (mService != null) {
+ try {
+ return mService.setDeviceOwner(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set device owner");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Used to determine if a particular package has been registered as a Device Owner admin.
+ * Device Owner admins cannot be deactivated by the user unless the Device Owner itself allows
+ * it. And Device Owner packages cannot be uninstalled, once registered.
+ * @param packageName the package name to check against the registered device owner.
+ * @return whether or not the package is registered as the Device Owner.
+ */
+ public boolean isDeviceOwner(String packageName) {
+ if (mService != null) {
+ try {
+ return mService.isDeviceOwner(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to check device owner");
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ public String getDeviceOwner() {
+ if (mService != null) {
+ try {
+ return mService.getDeviceOwner();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to get device owner");
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e061ab3..b2a65bf 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -97,4 +97,8 @@ interface IDevicePolicyManager {
int numbers, int symbols, int nonletter, int userHandle);
void reportFailedPasswordAttempt(int userHandle);
void reportSuccessfulPasswordAttempt(int userHandle);
+
+ boolean setDeviceOwner(String packageName);
+ boolean isDeviceOwner(String packageName);
+ String getDeviceOwner();
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 3425765..37fddcb 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -477,21 +477,31 @@ public abstract class BackupAgent extends ContextWrapper {
}
} else {
// Not a supported location
- Log.i(TAG, "Data restored from non-app domain " + domain + ", ignoring");
+ Log.i(TAG, "Unrecognized domain " + domain);
}
// Now that we've figured out where the data goes, send it on its way
if (basePath != null) {
+ // Canonicalize the nominal path and verify that it lies within the stated domain
File outFile = new File(basePath, path);
- if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outFile.getPath());
- onRestoreFile(data, size, outFile, type, mode, mtime);
- } else {
- // Not a supported output location? We need to consume the data
- // anyway, so just use the default "copy the data out" implementation
- // with a null destination.
- if (DEBUG) Log.i(TAG, "[ skipping data from unsupported domain " + domain + "]");
- FullBackup.restoreFile(data, size, type, mode, mtime, null);
+ String outPath = outFile.getCanonicalPath();
+ if (outPath.startsWith(basePath + File.separatorChar)) {
+ if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath);
+ onRestoreFile(data, size, outFile, type, mode, mtime);
+ return;
+ } else {
+ // Attempt to restore to a path outside the file's nominal domain.
+ if (DEBUG) {
+ Log.e(TAG, "Cross-domain restore attempt: " + outPath);
+ }
+ }
}
+
+ // Not a supported output location, or bad path: we need to consume the data
+ // anyway, so just use the default "copy the data out" implementation
+ // with a null destination.
+ if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]");
+ FullBackup.restoreFile(data, size, type, mode, mtime, null);
}
// ----- Core implementation -----
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 32cc7fd..02401dc 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -398,6 +398,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public String[] resourceDirs;
/**
+ * String retrieved from the seinfo tag found in selinux policy. This value
+ * is useful in setting an SELinux security context on the process as well
+ * as its data directory.
+ *
+ * {@hide}
+ */
+ public String seinfo;
+
+ /**
* Paths to all shared libraries this application is linked against. This
* field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
* PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
@@ -477,6 +486,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
if (resourceDirs != null) {
pw.println(prefix + "resourceDirs=" + resourceDirs);
}
+ if (seinfo != null) {
+ pw.println(prefix + "seinfo=" + seinfo);
+ }
pw.println(prefix + "dataDir=" + dataDir);
if (sharedLibraryFiles != null) {
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
@@ -544,6 +556,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
publicSourceDir = orig.publicSourceDir;
nativeLibraryDir = orig.nativeLibraryDir;
resourceDirs = orig.resourceDirs;
+ seinfo = orig.seinfo;
sharedLibraryFiles = orig.sharedLibraryFiles;
dataDir = orig.dataDir;
uid = orig.uid;
@@ -583,6 +596,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeString(publicSourceDir);
dest.writeString(nativeLibraryDir);
dest.writeStringArray(resourceDirs);
+ dest.writeString(seinfo);
dest.writeStringArray(sharedLibraryFiles);
dest.writeString(dataDir);
dest.writeInt(uid);
@@ -621,6 +635,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
publicSourceDir = source.readString();
nativeLibraryDir = source.readString();
resourceDirs = source.readStringArray();
+ seinfo = source.readString();
sharedLibraryFiles = source.readStringArray();
dataDir = source.readString();
uid = source.readInt();
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index ce1276f..786439e 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -119,6 +119,8 @@ public class TrafficStats {
* Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
* used internally by system services like {@link DownloadManager} when
* performing traffic on behalf of an application.
+ *
+ * @see #clearThreadStatsTag()
*/
public static void setThreadStatsTag(int tag) {
NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
@@ -128,11 +130,19 @@ public class TrafficStats {
* Get the active tag used when accounting {@link Socket} traffic originating
* from the current thread. Only one active tag per thread is supported.
* {@link #tagSocket(Socket)}.
+ *
+ * @see #setThreadStatsTag(int)
*/
public static int getThreadStatsTag() {
return NetworkManagementSocketTagger.getThreadSocketStatsTag();
}
+ /**
+ * Clear any active tag set to account {@link Socket} traffic originating
+ * from the current thread.
+ *
+ * @see #setThreadStatsTag(int)
+ */
public static void clearThreadStatsTag() {
NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
}
@@ -148,7 +158,7 @@ public class TrafficStats {
* To take effect, caller must hold
* {@link android.Manifest.permission#UPDATE_DEVICE_STATS} permission.
*
- * {@hide}
+ * @hide
*/
public static void setThreadStatsUid(int uid) {
NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
@@ -260,10 +270,13 @@ public class TrafficStats {
}
/**
- * Get the total number of packets transmitted through the mobile interface.
- *
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileTxPackets() {
long total = 0;
@@ -274,10 +287,13 @@ public class TrafficStats {
}
/**
- * Get the total number of packets received through the mobile interface.
- *
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets received across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileRxPackets() {
long total = 0;
@@ -288,10 +304,13 @@ public class TrafficStats {
}
/**
- * Get the total number of bytes transmitted through the mobile interface.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileTxBytes() {
long total = 0;
@@ -302,10 +321,13 @@ public class TrafficStats {
}
/**
- * Get the total number of bytes received through the mobile interface.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes received across mobile networks since device boot.
+ * Counts packets across all mobile network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileRxBytes() {
long total = 0;
@@ -339,85 +361,73 @@ public class TrafficStats {
return total;
}
- /**
- * Get the total number of packets transmitted through the specified interface.
- *
- * @return number of packets. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
public static long getTxPackets(String iface) {
return nativeGetIfaceStat(iface, TYPE_TX_PACKETS);
}
- /**
- * Get the total number of packets received through the specified interface.
- *
- * @return number of packets. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
public static long getRxPackets(String iface) {
return nativeGetIfaceStat(iface, TYPE_RX_PACKETS);
}
- /**
- * Get the total number of bytes transmitted through the specified interface.
- *
- * @return number of bytes. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
public static long getTxBytes(String iface) {
return nativeGetIfaceStat(iface, TYPE_TX_BYTES);
}
- /**
- * Get the total number of bytes received through the specified interface.
- *
- * @return number of bytes. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
public static long getRxBytes(String iface) {
return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
}
/**
- * Get the total number of packets sent through all network interfaces.
- *
- * @return the number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalTxPackets() {
return nativeGetTotalStat(TYPE_TX_PACKETS);
}
/**
- * Get the total number of packets received through all network interfaces.
- *
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets received since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalRxPackets() {
return nativeGetTotalStat(TYPE_RX_PACKETS);
}
/**
- * Get the total number of bytes sent through all network interfaces.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalTxBytes() {
return nativeGetTotalStat(TYPE_TX_BYTES);
}
/**
- * Get the total number of bytes received through all network interfaces.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes received since device boot. Counts packets across
+ * all network interfaces, and always increases monotonically since device
+ * boot. Statistics are measured at the network layer, so they include both
+ * TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalRxBytes() {
return nativeGetTotalStat(TYPE_RX_BYTES);
@@ -580,6 +590,7 @@ public class TrafficStats {
* special permission.
*/
private static NetworkStats getDataLayerSnapshotForUid(Context context) {
+ // TODO: take snapshot locally, since proc file is now visible
final int uid = android.os.Process.myUid();
try {
return getStatsService().getDataLayerSnapshotForUid(uid);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 4c2d7a6..2e8092a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -34,6 +34,7 @@ interface IUserManager {
Bitmap getUserIcon(int userHandle);
List<UserInfo> getUsers(boolean excludeDying);
UserInfo getUserInfo(int userHandle);
+ boolean isRestricted();
void setGuestEnabled(boolean enable);
boolean isGuestEnabled();
void wipeUser(int userHandle);
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index c9adf45..729c64b 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -138,8 +138,6 @@ public final class SystemClock {
/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
- * <b>Note:</b> This value may get reset occasionally (before it would
- * otherwise wrap around).
*
* @return milliseconds of non-sleep uptime since boot.
*/
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7c05528..622308f 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -37,48 +37,56 @@ public class UserManager {
private final Context mContext;
/**
- * @hide
- * Key for user restrictions. Specifies if a user is allowed to add or remove accounts.
+ * Key for user restrictions. Specifies if a user is disallowed from adding and removing
+ * accounts.
+ * The default value is <code>false</code>.
+ * <p/>
* Type: Boolean
* @see #setUserRestrictions(Bundle)
* @see #getUserRestrictions()
*/
- public static final String ALLOW_MODIFY_ACCOUNTS = "modify_accounts";
+ public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
/**
- * @hide
- * Key for user restrictions. Specifies if a user is allowed to change Wi-Fi access points.
+ * Key for user restrictions. Specifies if a user is disallowed from changing Wi-Fi
+ * access points.
+ * The default value is <code>false</code>.
+ * <p/>
* Type: Boolean
* @see #setUserRestrictions(Bundle)
* @see #getUserRestrictions()
*/
- public static final String ALLOW_CONFIG_WIFI = "config_wifi";
+ public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
/**
- * @hide
- * Key for user restrictions. Specifies if a user is allowed to install applications.
+ * Key for user restrictions. Specifies if a user is disallowed from installing applications.
+ * The default value is <code>false</code>.
+ * <p/>
* Type: Boolean
* @see #setUserRestrictions(Bundle)
* @see #getUserRestrictions()
*/
- public static final String ALLOW_INSTALL_APPS = "install_apps";
+ public static final String DISALLOW_INSTALL_APPS = "no_install_apps";
/**
- * @hide
- * Key for user restrictions. Specifies if a user is allowed to uninstall applications.
+ * Key for user restrictions. Specifies if a user is disallowed from uninstalling applications.
+ * The default value is <code>false</code>.
+ * <p/>
* Type: Boolean
* @see #setUserRestrictions(Bundle)
* @see #getUserRestrictions()
*/
- public static final String ALLOW_UNINSTALL_APPS = "uninstall_apps";
+ public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
- /** @hide *
- * Key for user restrictions. Specifies if a user is allowed to toggle location sharing.
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from toggling location sharing.
+ * The default value is <code>false</code>.
+ * <p/>
* Type: Boolean
* @see #setUserRestrictions(Bundle)
* @see #getUserRestrictions()
*/
- public static final String ALLOW_CONFIG_LOCATION_ACCESS = "config_location_access";
+ public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
/** @hide */
public UserManager(Context context, IUserManager service) {
@@ -129,11 +137,14 @@ public class UserManager {
}
/**
- * @hide
+ * Used to check if the user making this call is a restricted user. Restricted users may have
+ * application restrictions imposed on them. All apps should default to the most restrictive
+ * version, unless they have specific restrictions available through a call to
+ * {@link Context#getApplicationRestrictions()}.
*/
public boolean isUserRestricted() {
try {
- return mService.getUserInfo(getUserHandle()).isRestricted();
+ return mService.isRestricted();
} catch (RemoteException re) {
Log.w(TAG, "Could not check if user restricted ", re);
return false;
@@ -189,12 +200,19 @@ public class UserManager {
}
}
- /** @hide */
+ /**
+ * Returns the user-wide restrictions imposed on this user.
+ * @return a Bundle containing all the restrictions.
+ */
public Bundle getUserRestrictions() {
return getUserRestrictions(Process.myUserHandle());
}
- /** @hide */
+ /**
+ * Returns the user-wide restrictions imposed on the user specified by <code>userHandle</code>.
+ * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+ * @return a Bundle containing all the restrictions.
+ */
public Bundle getUserRestrictions(UserHandle userHandle) {
try {
return mService.getUserRestrictions(userHandle.getIdentifier());
@@ -204,12 +222,21 @@ public class UserManager {
}
}
- /** @hide */
+ /**
+ * Sets all the user-wide restrictions for this user.
+ * Requires the MANAGE_USERS permission.
+ * @param restrictions the Bundle containing all the restrictions.
+ */
public void setUserRestrictions(Bundle restrictions) {
setUserRestrictions(restrictions, Process.myUserHandle());
}
- /** @hide */
+ /**
+ * Sets all the user-wide restrictions for the specified user.
+ * Requires the MANAGE_USERS permission.
+ * @param restrictions the Bundle containing all the restrictions.
+ * @param userHandle the UserHandle of the user for whom to set the restrictions.
+ */
public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
try {
mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
@@ -218,7 +245,26 @@ public class UserManager {
}
}
- /** @hide */
+ /**
+ * Sets the value of a specific restriction.
+ * Requires the MANAGE_USERS permission.
+ * @param key the key of the restriction
+ * @param value the value for the restriction
+ */
+ public void setUserRestriction(String key, boolean value) {
+ Bundle bundle = getUserRestrictions();
+ bundle.putBoolean(key, value);
+ setUserRestrictions(bundle);
+ }
+
+ /**
+ * @hide
+ * Sets the value of a specific restriction on a specific user.
+ * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param key the key of the restriction
+ * @param value the value for the restriction
+ * @param userHandle the user whose restriction is to be changed.
+ */
public void setUserRestriction(String key, boolean value, UserHandle userHandle) {
Bundle bundle = getUserRestrictions(userHandle);
bundle.putBoolean(key, value);
@@ -467,7 +513,7 @@ public class UserManager {
* @hide
*/
public boolean isLocationSharingToggleAllowed() {
- return getUserRestrictions().getBoolean(ALLOW_CONFIG_LOCATION_ACCESS);
+ return !getUserRestrictions().getBoolean(DISALLOW_SHARE_LOCATION, false);
}
/**
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 7e9d811..5a88cf6 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -39,7 +39,6 @@ public class DateUtils
{
private static final Object sLock = new Object();
private static Configuration sLastConfig;
- private static java.text.DateFormat sStatusTimeFormat;
private static String sElapsedFormatMMSS;
private static String sElapsedFormatHMMSS;
@@ -95,14 +94,14 @@ public class DateUtils
// translated.
/**
* This is not actually the preferred 24-hour date format in all locales.
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final String HOUR_MINUTE_24 = "%H:%M";
public static final String MONTH_FORMAT = "%B";
/**
* This is not actually a useful month name in all locales.
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final String ABBREV_MONTH_FORMAT = "%b";
@@ -118,7 +117,7 @@ public class DateUtils
// The index is constructed from a bit-wise OR of the boolean values:
// {showTime, showYear, showWeekDay}. For example, if showYear and
// showWeekDay are both true, then the index would be 3.
- /** @deprecated do not use. */
+ /** @deprecated Do not use. */
public static final int sameYearTable[] = {
com.android.internal.R.string.same_year_md1_md2,
com.android.internal.R.string.same_year_wday1_md1_wday2_md2,
@@ -145,7 +144,7 @@ public class DateUtils
// The index is constructed from a bit-wise OR of the boolean values:
// {showTime, showYear, showWeekDay}. For example, if showYear and
// showWeekDay are both true, then the index would be 3.
- /** @deprecated do not use. */
+ /** @deprecated Do not use. */
public static final int sameMonthTable[] = {
com.android.internal.R.string.same_month_md1_md2,
com.android.internal.R.string.same_month_wday1_md1_wday2_md2,
@@ -172,7 +171,7 @@ public class DateUtils
*
* @more <p>
* e.g. "Sunday" or "January"
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_LONG = 10;
@@ -183,7 +182,7 @@ public class DateUtils
*
* @more <p>
* e.g. "Sun" or "Jan"
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_MEDIUM = 20;
@@ -195,7 +194,7 @@ public class DateUtils
* <p>e.g. "Su" or "Jan"
* <p>In most languages, the results returned for LENGTH_SHORT will be the same as
* the results returned for {@link #LENGTH_MEDIUM}.
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_SHORT = 30;
@@ -204,7 +203,7 @@ public class DateUtils
* Request an even shorter abbreviated version of the name.
* Do not use this. Currently this will always return the same result
* as {@link #LENGTH_SHORT}.
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_SHORTER = 40;
@@ -216,7 +215,7 @@ public class DateUtils
* <p>e.g. "S", "T", "T" or "J"
* <p>In some languages, the results returned for LENGTH_SHORTEST will be the same as
* the results returned for {@link #LENGTH_SHORT}.
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_SHORTEST = 50;
@@ -232,7 +231,7 @@ public class DateUtils
* Undefined lengths will return {@link #LENGTH_MEDIUM}
* but may return something different in the future.
* @throws IndexOutOfBoundsException if the dayOfWeek is out of bounds.
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static String getDayOfWeekString(int dayOfWeek, int abbrev) {
@@ -254,7 +253,7 @@ public class DateUtils
* @param ampm Either {@link Calendar#AM Calendar.AM} or {@link Calendar#PM Calendar.PM}.
* @throws IndexOutOfBoundsException if the ampm is out of bounds.
* @return Localized version of "AM" or "PM".
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static String getAMPMString(int ampm) {
@@ -270,7 +269,7 @@ public class DateUtils
* Undefined lengths will return {@link #LENGTH_MEDIUM}
* but may return something different in the future.
* @return Localized month of the year.
- * @deprecated use {@link java.text.SimpleDateFormat} instead.
+ * @deprecated Use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static String getMonthString(int month, int abbrev) {
@@ -514,7 +513,6 @@ public class DateUtils
Configuration cfg = r.getConfiguration();
if (sLastConfig == null || !sLastConfig.equals(cfg)) {
sLastConfig = cfg;
- sStatusTimeFormat = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT);
sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss);
sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss);
}
@@ -651,99 +649,6 @@ public class DateUtils
}
/**
- * Return a string containing the date and time in RFC2445 format.
- * Ensures that the time is written in UTC. The Calendar class doesn't
- * really help out with this, so this is slower than it ought to be.
- *
- * @param cal the date and time to write
- * @hide
- * @deprecated use {@link android.text.format.Time}
- */
- public static String writeDateTime(Calendar cal)
- {
- TimeZone tz = TimeZone.getTimeZone("GMT");
- GregorianCalendar c = new GregorianCalendar(tz);
- c.setTimeInMillis(cal.getTimeInMillis());
- return writeDateTime(c, true);
- }
-
- /**
- * Return a string containing the date and time in RFC2445 format.
- *
- * @param cal the date and time to write
- * @param zulu If the calendar is in UTC, pass true, and a Z will
- * be written at the end as per RFC2445. Otherwise, the time is
- * considered in localtime.
- * @hide
- * @deprecated use {@link android.text.format.Time}
- */
- public static String writeDateTime(Calendar cal, boolean zulu)
- {
- StringBuilder sb = new StringBuilder();
- sb.ensureCapacity(16);
- if (zulu) {
- sb.setLength(16);
- sb.setCharAt(15, 'Z');
- } else {
- sb.setLength(15);
- }
- return writeDateTime(cal, sb);
- }
-
- /**
- * Return a string containing the date and time in RFC2445 format.
- *
- * @param cal the date and time to write
- * @param sb a StringBuilder to use. It is assumed that setLength
- * has already been called on sb to the appropriate length
- * which is sb.setLength(zulu ? 16 : 15)
- * @hide
- * @deprecated use {@link android.text.format.Time}
- */
- public static String writeDateTime(Calendar cal, StringBuilder sb)
- {
- int n;
-
- n = cal.get(Calendar.YEAR);
- sb.setCharAt(3, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(2, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(1, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(0, (char)('0'+n%10));
-
- n = cal.get(Calendar.MONTH) + 1;
- sb.setCharAt(5, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(4, (char)('0'+n%10));
-
- n = cal.get(Calendar.DAY_OF_MONTH);
- sb.setCharAt(7, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(6, (char)('0'+n%10));
-
- sb.setCharAt(8, 'T');
-
- n = cal.get(Calendar.HOUR_OF_DAY);
- sb.setCharAt(10, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(9, (char)('0'+n%10));
-
- n = cal.get(Calendar.MINUTE);
- sb.setCharAt(12, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(11, (char)('0'+n%10));
-
- n = cal.get(Calendar.SECOND);
- sb.setCharAt(14, (char)('0'+n%10));
- n /= 10;
- sb.setCharAt(13, (char)('0'+n%10));
-
- return sb.toString();
- }
-
- /**
* Formats a date or a time range according to the local conventions.
* <p>
* Note that this is a convenience method. Using it involves creating an
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 121c6f2..9c98b98 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -95,16 +95,12 @@ public final class Formatter {
}
/**
- * Returns a string in the canonical IP format ###.###.###.### from a packed integer containing
- * the IP address. The IP address is expected to be in little-endian format (LSB first). That
- * is, 0x01020304 will return "4.3.2.1".
+ * Returns a string in the canonical IPv4 format ###.###.###.### from a packed integer
+ * containing the IP address. The IPv4 address is expected to be in little-endian
+ * format (LSB first). That is, 0x01020304 will return "4.3.2.1".
*
- * @param ipv4Address the IP address as a packed integer with LSB first.
- * @return string with canonical IP address format.
- *
- * @deprecated this method doesn't support IPv6 addresses. Prefer {@link
- * java.net.InetAddress#getHostAddress()}, which supports both IPv4 and
- * IPv6 addresses.
+ * @deprecated Use {@link java.net.InetAddress#getHostAddress()}, which supports both IPv4 and
+ * IPv6 addresses. This method does not support IPv6 addresses.
*/
@Deprecated
public static String formatIpAddress(int ipv4Address) {
diff --git a/core/java/android/view/Overlay.java b/core/java/android/view/Overlay.java
index 210bc31..6630752 100644
--- a/core/java/android/view/Overlay.java
+++ b/core/java/android/view/Overlay.java
@@ -18,9 +18,10 @@ package android.view;
import android.graphics.drawable.Drawable;
/**
- * An overlay is an extra layer that sits on top of a View (the "host view") which is drawn after
- * all other content in that view (including children, if the view is a ViewGroup). Interaction
- * with the overlay layer is done in terms of adding/removing views and drawables.
+ * An overlay is an extra layer that sits on top of a View (the "host view")
+ * which is drawn after all other content in that view (including children,
+ * if the view is a ViewGroup). Interaction with the overlay layer is done in
+ * terms of adding/removing views and drawables.
*
* @see android.view.View#getOverlay()
*/
@@ -47,10 +48,16 @@ public interface Overlay {
void remove(Drawable drawable);
/**
- * Adds a View to the overlay. The bounds of the added view should be relative to
- * the host view. Any view added to the overlay should be removed when it is no longer
- * needed or no longer visible. The view must not be parented elsewhere when it is added
- * to the overlay.
+ * Adds a View to the overlay. The bounds of the added view should be
+ * relative to the host view. Any view added to the overlay should be
+ * removed when it is no longer needed or no longer visible.
+ *
+ * <p>If the view has a parent, the view will be removed from that parent
+ * before being added to the overlay. Also, the view will be repositioned
+ * such that it is in the same relative location inside the activity. For
+ * example, if the view's current parent lies 100 pixels to the right
+ * and 200 pixels down from the origin of the overlay's
+ * host view, then the view will be offset by (100, 200).</p>
*
* @param view The View to be added to the overlay. The added view will be
* drawn when the overlay is drawn.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 34f5a2b..a5b3c8f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12098,7 +12098,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
//System.out.println("Attached! " + this);
mAttachInfo = info;
if (mOverlay != null) {
- mOverlay.mAttachInfo = info;
+ mOverlay.dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
@@ -12169,7 +12169,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo = null;
if (mOverlay != null) {
- mOverlay.mAttachInfo = null;
+ mOverlay.dispatchDetachedFromWindow();
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d63f7bc..688e17e 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1868,13 +1868,37 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+
+ if (mOverlay != null) {
+ ViewOverlay overlay = (ViewOverlay) mOverlay;
+ // Check to see whether the overlay can handle the event
+ final View child = mOverlay;
+ if (canViewReceivePointerEvents(child) &&
+ isTransformedTouchPointInView(x, y, child, null)) {
+ newTouchTarget = getTouchTarget(child);
+ if (newTouchTarget != null) {
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ } else {
+ resetCancelNextUpFlag(child);
+ if (dispatchTransformedTouchEvent(ev, false, child,
+ idBitsToAssign)) {
+ mLastTouchDownTime = ev.getDownTime();
+ mLastTouchDownX = ev.getX();
+ mLastTouchDownY = ev.getY();
+ newTouchTarget = addTouchTarget(child, idBitsToAssign);
+ alreadyDispatchedToNewTouchTarget = true;
+ }
+ }
+ }
+ }
+
final int childrenCount = mChildrenCount;
- if (childrenCount != 0 || mOverlay != null) {
+ if (newTouchTarget == null && childrenCount != 0) {
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
- final float x = ev.getX(actionIndex);
- final float y = ev.getY(actionIndex);
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
@@ -1906,27 +1930,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
break;
}
}
- if (mOverlay != null && newTouchTarget == null) {
- // Check to see whether the overlay can handle the event
- final View child = mOverlay;
- if (canViewReceivePointerEvents(child) &&
- isTransformedTouchPointInView(x, y, child, null)) {
- newTouchTarget = getTouchTarget(child);
- if (newTouchTarget != null) {
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- } else {
- resetCancelNextUpFlag(child);
- if (dispatchTransformedTouchEvent(ev, false, child,
- idBitsToAssign)) {
- mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownX = ev.getX();
- mLastTouchDownY = ev.getY();
- newTouchTarget = addTouchTarget(child, idBitsToAssign);
- alreadyDispatchedToNewTouchTarget = true;
- }
- }
- }
- }
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
@@ -1957,7 +1960,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
- || intercepted;
+ || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index 939377d..8b18d53 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -23,17 +23,21 @@ import android.graphics.drawable.Drawable;
import java.util.ArrayList;
/**
- * ViewOverlay is a container that View uses to host all objects (views and drawables) that
- * are added to its "overlay", gotten through {@link View#getOverlay()}. Views and drawables are
- * added to the overlay via the add/remove methods in this class. These views and drawables are
- * drawn whenever the view itself is drawn; first the view draws its own content (and children,
- * if it is a ViewGroup), then it draws its overlay (if it has one).
+ * ViewOverlay is a container that View uses to host all objects (views and
+ * drawables) that are added to its "overlay", gotten through
+ * {@link View#getOverlay()}. Views and drawables are added to the overlay
+ * via the add/remove methods in this class. These views and drawables are
+ * drawn whenever the view itself is drawn; first the view draws its own
+ * content (and children, if it is a ViewGroup), then it draws its overlay
+ * (if it has one).
*
- * Besides managing and drawing the list of drawables, this class serves two purposes:
+ * Besides managing and drawing the list of drawables, this class serves
+ * two purposes:
* (1) it noops layout calls because children are absolutely positioned and
- * (2) it forwards all invalidation calls to its host view. The invalidation redirect is
- * necessary because the overlay is not a child of the host view and invalidation cannot
- * therefore follow the normal path up through the parent hierarchy.
+ * (2) it forwards all invalidation calls to its host view. The invalidation
+ * redirect is necessary because the overlay is not a child of the host view
+ * and invalidation cannot therefore follow the normal path up through the
+ * parent hierarchy.
*
* @hide
*/
@@ -85,6 +89,22 @@ class ViewOverlay extends ViewGroup implements Overlay {
@Override
public void add(View child) {
+ int deltaX = 0;
+ int deltaY = 0;
+ if (child.getParent() instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) child.getParent();
+ if (parent != mHostView) {
+ // Moving to different container; figure out how to position child such that
+ // it is in the same location on the screen
+ int[] parentLocation = new int[2];
+ int[] hostViewLocation = new int[2];
+ parent.getLocationOnScreen(parentLocation);
+ mHostView.getLocationOnScreen(hostViewLocation);
+ child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]);
+ child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]);
+ }
+ parent.removeView(child);
+ }
super.addView(child);
}
@@ -133,7 +153,6 @@ class ViewOverlay extends ViewGroup implements Overlay {
public void invalidate(Rect dirty) {
super.invalidate(dirty);
if (mHostView != null) {
- dirty.offset(getLeft(), getTop());
mHostView.invalidate(dirty);
}
}
@@ -203,7 +222,15 @@ class ViewOverlay extends ViewGroup implements Overlay {
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
if (mHostView != null) {
- mHostView.invalidate(dirty);
+ dirty.offset(location[0], location[1]);
+ if (mHostView instanceof ViewGroup) {
+ location[0] = 0;
+ location[1] = 0;
+ super.invalidateChildInParent(location, dirty);
+ return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty);
+ } else {
+ invalidate(dirty);
+ }
}
return null;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b30dc83..401db1f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2952,6 +2952,8 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_DISPATCH_DONE_ANIMATING = 22;
private final static int MSG_INVALIDATE_WORLD = 23;
private final static int MSG_WINDOW_MOVED = 24;
+ private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 25;
+ private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 26;
final class ViewRootHandler extends Handler {
@Override
@@ -3003,6 +3005,10 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_DISPATCH_DONE_ANIMATING";
case MSG_WINDOW_MOVED:
return "MSG_WINDOW_MOVED";
+ case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
+ return "MSG_ENQUEUE_X_AXIS_KEY_REPEAT";
+ case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT:
+ return "MSG_ENQUEUE_Y_AXIS_KEY_REPEAT";
}
return super.getMessageName(message);
}
@@ -3229,6 +3235,18 @@ public final class ViewRootImpl implements ViewParent,
invalidateWorld(mView);
}
} break;
+ case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
+ case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
+ KeyEvent oldEvent = (KeyEvent)msg.obj;
+ KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent, SystemClock.uptimeMillis(),
+ oldEvent.getRepeatCount() + 1);
+ if (mAttachInfo.mHasWindowFocus) {
+ enqueueInputEvent(e);
+ Message m = obtainMessage(msg.what, e);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, mViewConfiguration.getKeyRepeatDelay());
+ }
+ } break;
}
}
}
@@ -3677,6 +3695,7 @@ public final class ViewRootImpl implements ViewParent,
if (xDirection != mLastJoystickXDirection) {
if (mLastJoystickXKeyCode != 0) {
+ mHandler.removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
enqueueInputEvent(new KeyEvent(time, time,
KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
@@ -3688,14 +3707,19 @@ public final class ViewRootImpl implements ViewParent,
if (xDirection != 0 && synthesizeNewKeys) {
mLastJoystickXKeyCode = xDirection > 0
? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
- enqueueInputEvent(new KeyEvent(time, time,
+ final KeyEvent e = new KeyEvent(time, time,
KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(e);
+ Message m = mHandler.obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
+ m.setAsynchronous(true);
+ mHandler.sendMessageDelayed(m, mViewConfiguration.getKeyRepeatTimeout());
}
}
if (yDirection != mLastJoystickYDirection) {
if (mLastJoystickYKeyCode != 0) {
+ mHandler.removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
enqueueInputEvent(new KeyEvent(time, time,
KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
@@ -3707,9 +3731,13 @@ public final class ViewRootImpl implements ViewParent,
if (yDirection != 0 && synthesizeNewKeys) {
mLastJoystickYKeyCode = yDirection > 0
? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
- enqueueInputEvent(new KeyEvent(time, time,
+ final KeyEvent e = new KeyEvent(time, time,
KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(e);
+ Message m = mHandler.obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
+ m.setAsynchronous(true);
+ mHandler.sendMessageDelayed(m, mViewConfiguration.getKeyRepeatTimeout());
}
}
}
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 06dadb0..7c961bd 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -66,20 +66,17 @@ public class AppSecurityPermissions {
private final static String TAG = "AppSecurityPermissions";
private final static boolean localLOGV = false;
- private Context mContext;
- private LayoutInflater mInflater;
- private PackageManager mPm;
- private PackageInfo mInstalledPackageInfo;
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+ private final PackageManager mPm;
private final Map<String, MyPermissionGroupInfo> mPermGroups
= new HashMap<String, MyPermissionGroupInfo>();
private final List<MyPermissionGroupInfo> mPermGroupsList
= new ArrayList<MyPermissionGroupInfo>();
- private final PermissionGroupInfoComparator mPermGroupComparator;
- private final PermissionInfoComparator mPermComparator;
- private List<MyPermissionInfo> mPermsList;
- private CharSequence mNewPermPrefix;
- private Drawable mNormalIcon;
- private Drawable mDangerousIcon;
+ private final PermissionGroupInfoComparator mPermGroupComparator = new PermissionGroupInfoComparator();
+ private final PermissionInfoComparator mPermComparator = new PermissionInfoComparator();
+ private final List<MyPermissionInfo> mPermsList = new ArrayList<MyPermissionInfo>();
+ private final CharSequence mNewPermPrefix;
static class MyPermissionGroupInfo extends PermissionGroupInfo {
CharSequence mLabel;
@@ -113,7 +110,7 @@ public class AppSecurityPermissions {
}
}
- static class MyPermissionInfo extends PermissionInfo {
+ private static class MyPermissionInfo extends PermissionInfo {
CharSequence mLabel;
/**
@@ -132,19 +129,9 @@ public class AppSecurityPermissions {
*/
boolean mNew;
- MyPermissionInfo() {
- }
-
MyPermissionInfo(PermissionInfo info) {
super(info);
}
-
- MyPermissionInfo(MyPermissionInfo info) {
- super(info);
- mNewReqFlags = info.mNewReqFlags;
- mExistingReqFlags = info.mExistingReqFlags;
- mNew = info.mNew;
- }
}
public static class PermissionItemView extends LinearLayout implements View.OnClickListener {
@@ -233,25 +220,16 @@ public class AppSecurityPermissions {
}
}
- public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
+ private AppSecurityPermissions(Context context) {
mContext = context;
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPm = mContext.getPackageManager();
- loadResources();
- mPermComparator = new PermissionInfoComparator();
- mPermGroupComparator = new PermissionGroupInfoComparator();
- for (PermissionInfo pi : permList) {
- mPermsList.add(new MyPermissionInfo(pi));
- }
- setPermissions(mPermsList);
+ // Pick up from framework resources instead.
+ mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
}
-
+
public AppSecurityPermissions(Context context, String packageName) {
- mContext = context;
- mPm = mContext.getPackageManager();
- loadResources();
- mPermComparator = new PermissionInfoComparator();
- mPermGroupComparator = new PermissionGroupInfoComparator();
- mPermsList = new ArrayList<MyPermissionInfo>();
+ this(context);
Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
PackageInfo pkgInfo;
try {
@@ -264,19 +242,12 @@ public class AppSecurityPermissions {
if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
}
- for(MyPermissionInfo tmpInfo : permSet) {
- mPermsList.add(tmpInfo);
- }
+ mPermsList.addAll(permSet);
setPermissions(mPermsList);
}
public AppSecurityPermissions(Context context, PackageInfo info) {
- mContext = context;
- mPm = mContext.getPackageManager();
- loadResources();
- mPermComparator = new PermissionInfoComparator();
- mPermGroupComparator = new PermissionGroupInfoComparator();
- mPermsList = new ArrayList<MyPermissionInfo>();
+ this(context);
Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
if(info == null) {
return;
@@ -300,23 +271,14 @@ public class AppSecurityPermissions {
sharedUid = mPm.getUidForSharedUser(info.sharedUserId);
getAllUsedPermissions(sharedUid, permSet);
} catch (NameNotFoundException e) {
- Log.w(TAG, "Could'nt retrieve shared user id for:"+info.packageName);
+ Log.w(TAG, "Couldn't retrieve shared user id for: " + info.packageName);
}
}
// Retrieve list of permissions
- for (MyPermissionInfo tmpInfo : permSet) {
- mPermsList.add(tmpInfo);
- }
+ mPermsList.addAll(permSet);
setPermissions(mPermsList);
}
- private void loadResources() {
- // Pick up from framework resources instead.
- mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
- mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
- mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
- }
-
/**
* Utility to retrieve a view displaying a single permission. This provides
* the old UI layout for permissions; it is only here for the device admin
@@ -332,10 +294,6 @@ public class AppSecurityPermissions {
description, dangerous, icon);
}
- public PackageInfo getInstalledPackageInfo() {
- return mInstalledPackageInfo;
- }
-
private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
if(sharedPkgList == null || (sharedPkgList.length == 0)) {
@@ -346,17 +304,12 @@ public class AppSecurityPermissions {
}
}
- private void getPermissionsForPackage(String packageName,
- Set<MyPermissionInfo> permSet) {
- PackageInfo pkgInfo;
+ private void getPermissionsForPackage(String packageName, Set<MyPermissionInfo> permSet) {
try {
- pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
- return;
- }
- if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
+ PackageInfo pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
extractPerms(pkgInfo, permSet, pkgInfo);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Couldn't retrieve permissions for package: " + packageName);
}
}
@@ -367,7 +320,6 @@ public class AppSecurityPermissions {
if ((strList == null) || (strList.length == 0)) {
return;
}
- mInstalledPackageInfo = installedPkgInfo;
for (int i=0; i<strList.length; i++) {
String permName = strList[i];
// If we are only looking at an existing app, then we only
@@ -471,8 +423,6 @@ public class AppSecurityPermissions {
}
public View getPermissionsView(int which) {
- mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
LinearLayout permsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
LinearLayout displayList = (LinearLayout) permsView.findViewById(R.id.perms_list);
View noPermsView = permsView.findViewById(R.id.no_permissions);
@@ -557,16 +507,25 @@ public class AppSecurityPermissions {
private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags,
int existingReqFlags) {
final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- // Dangerous and normal permissions are always shown to the user.
- if (base == PermissionInfo.PROTECTION_DANGEROUS ||
- base == PermissionInfo.PROTECTION_NORMAL) {
+ final boolean isNormal = (base == PermissionInfo.PROTECTION_NORMAL);
+ final boolean isDangerous = (base == PermissionInfo.PROTECTION_DANGEROUS);
+ final boolean isRequired =
+ ((newReqFlags&PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
+ final boolean isDevelopment =
+ ((pInfo.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
+ final boolean wasGranted =
+ ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
+
+ // Dangerous and normal permissions are always shown to the user if the permission
+ // is required, or it was previously granted
+ if ((isNormal || isDangerous) && (isRequired || wasGranted)) {
return true;
}
+
// Development permissions are only shown to the user if they are already
// granted to the app -- if we are installing an app and they are not
// already granted, they will not be granted as part of the install.
- if ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0
- && (pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+ if (isDevelopment && wasGranted) {
if (localLOGV) Log.i(TAG, "Special perm " + pInfo.name
+ ": protlevel=0x" + Integer.toHexString(pInfo.protectionLevel));
return true;
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 0dd567b..7b81aa8 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -36,6 +36,8 @@ import android.widget.ExpandableListConnector.PositionMetadata;
import java.util.ArrayList;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
/**
* A view that shows items in a vertically scrolling two-level list. This
* differs from the {@link ListView} by allowing two levels: groups which can
@@ -76,6 +78,10 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#ExpandableListView_childIndicatorLeft
* @attr ref android.R.styleable#ExpandableListView_childIndicatorRight
* @attr ref android.R.styleable#ExpandableListView_childDivider
+ * @attr ref android.R.styleable#ExpandableListView_indicatorStart
+ * @attr ref android.R.styleable#ExpandableListView_indicatorEnd
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorStart
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorEnd
*/
public class ExpandableListView extends ListView {
@@ -134,6 +140,12 @@ public class ExpandableListView extends ListView {
/** Right bound for drawing the indicator. */
private int mIndicatorRight;
+ /** Start bound for drawing the indicator. */
+ private int mIndicatorStart;
+
+ /** End bound for drawing the indicator. */
+ private int mIndicatorEnd;
+
/**
* Left bound for drawing the indicator of a child. Value of
* {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorLeft.
@@ -147,11 +159,28 @@ public class ExpandableListView extends ListView {
private int mChildIndicatorRight;
/**
+ * Start bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorStart.
+ */
+ private int mChildIndicatorStart;
+
+ /**
+ * End bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorEnd.
+ */
+ private int mChildIndicatorEnd;
+
+ /**
* Denotes when a child indicator should inherit this bound from the generic
* indicator bounds
*/
public static final int CHILD_INDICATOR_INHERIT = -1;
-
+
+ /**
+ * Denotes an undefined value for an indicator
+ */
+ private static final int INDICATOR_UNDEFINED = -2;
+
/** The indicator drawn next to a group. */
private Drawable mGroupIndicator;
@@ -202,30 +231,118 @@ public class ExpandableListView extends ListView {
super(context, attrs, defStyle);
TypedArray a =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ExpandableListView, defStyle,
- 0);
-
- mGroupIndicator = a
- .getDrawable(com.android.internal.R.styleable.ExpandableListView_groupIndicator);
- mChildIndicator = a
- .getDrawable(com.android.internal.R.styleable.ExpandableListView_childIndicator);
- mIndicatorLeft = a
- .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
- mIndicatorRight = a
- .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
+ context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ExpandableListView, defStyle, 0);
+
+ mGroupIndicator = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_groupIndicator);
+ mChildIndicator = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_childIndicator);
+ mIndicatorLeft = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
+ mIndicatorRight = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
if (mIndicatorRight == 0 && mGroupIndicator != null) {
mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
}
mChildIndicatorLeft = a.getDimensionPixelSize(
- com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft, CHILD_INDICATOR_INHERIT);
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft,
+ CHILD_INDICATOR_INHERIT);
mChildIndicatorRight = a.getDimensionPixelSize(
- com.android.internal.R.styleable.ExpandableListView_childIndicatorRight, CHILD_INDICATOR_INHERIT);
- mChildDivider = a.getDrawable(com.android.internal.R.styleable.ExpandableListView_childDivider);
-
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorRight,
+ CHILD_INDICATOR_INHERIT);
+ mChildDivider = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_childDivider);
+
+ if (!isRtlCompatibilityMode()) {
+ mIndicatorStart = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorStart,
+ INDICATOR_UNDEFINED);
+ mIndicatorEnd = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorEnd,
+ INDICATOR_UNDEFINED);
+
+ mChildIndicatorStart = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorStart,
+ CHILD_INDICATOR_INHERIT);
+ mChildIndicatorEnd = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorEnd,
+ CHILD_INDICATOR_INHERIT);
+ }
+
a.recycle();
}
-
-
+
+ /**
+ * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
+ * RTL not supported)
+ */
+ private boolean isRtlCompatibilityMode() {
+ final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ return targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport();
+ }
+
+ /**
+ * Return true if the application tag in the AndroidManifest has set "supportRtl" to true
+ */
+ private boolean hasRtlSupport() {
+ return mContext.getApplicationInfo().hasRtlSupport();
+ }
+
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ resolveIndicator();
+ resolveChildIndicator();
+ }
+
+ /**
+ * Resolve start/end indicator. start/end indicator always takes precedence over left/right
+ * indicator when defined.
+ */
+ private void resolveIndicator() {
+ final boolean isLayoutRtl = isLayoutRtl();
+ if (isLayoutRtl) {
+ if (mIndicatorStart >= 0) {
+ mIndicatorRight = mIndicatorStart;
+ }
+ if (mIndicatorEnd >= 0) {
+ mIndicatorLeft = mIndicatorEnd;
+ }
+ } else {
+ if (mIndicatorStart >= 0) {
+ mIndicatorLeft = mIndicatorStart;
+ }
+ if (mIndicatorEnd >= 0) {
+ mIndicatorRight = mIndicatorEnd;
+ }
+ }
+ if (mIndicatorRight == 0 && mGroupIndicator != null) {
+ mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
+ }
+ }
+
+ /**
+ * Resolve start/end child indicator. start/end child indicator always takes precedence over
+ * left/right child indicator when defined.
+ */
+ private void resolveChildIndicator() {
+ final boolean isLayoutRtl = isLayoutRtl();
+ if (isLayoutRtl) {
+ if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorRight = mChildIndicatorStart;
+ }
+ if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorLeft = mChildIndicatorEnd;
+ }
+ } else {
+ if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorLeft = mChildIndicatorStart;
+ }
+ if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorRight = mChildIndicatorEnd;
+ }
+ }
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw children, etc.
@@ -1053,8 +1170,26 @@ public class ExpandableListView extends ListView {
public void setChildIndicatorBounds(int left, int right) {
mChildIndicatorLeft = left;
mChildIndicatorRight = right;
+ resolveChildIndicator();
}
-
+
+ /**
+ * Sets the relative drawing bounds for the child indicator. For either, you can
+ * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
+ * indicator's bounds.
+ *
+ * @see #setIndicatorBounds(int, int)
+ * @param start The start position (relative to the start bounds of this View)
+ * to start drawing the indicator.
+ * @param end The end position (relative to the end bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setChildIndicatorBoundsRelative(int start, int end) {
+ mChildIndicatorStart = start;
+ mChildIndicatorEnd = end;
+ resolveChildIndicator();
+ }
+
/**
* Sets the indicator to be drawn next to a group.
*
@@ -1084,8 +1219,26 @@ public class ExpandableListView extends ListView {
public void setIndicatorBounds(int left, int right) {
mIndicatorLeft = left;
mIndicatorRight = right;
+ resolveIndicator();
}
-
+
+ /**
+ * Sets the relative drawing bounds for the indicators (at minimum, the group indicator
+ * is affected by this; the child indicator is affected by this if the
+ * child indicator bounds are set to inherit).
+ *
+ * @see #setChildIndicatorBounds(int, int)
+ * @param start The start position (relative to the start bounds of this View)
+ * to start drawing the indicator.
+ * @param end The end position (relative to the end bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setIndicatorBoundsRelative(int start, int end) {
+ mIndicatorStart = start;
+ mIndicatorEnd = end;
+ resolveIndicator();
+ }
+
/**
* Extra menu information specific to an {@link ExpandableListView} provided
* to the
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index eed3e67..eb2d1fe 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -27,6 +27,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SELinux;
import android.util.Log;
import com.android.org.bouncycastle.util.encoders.Base64;
@@ -64,6 +65,10 @@ public class LocalTransport extends IBackupTransport.Stub {
public LocalTransport(Context context) {
mContext = context;
+ mDataDir.mkdirs();
+ if (!SELinux.restorecon(mDataDir)) {
+ Log.e(TAG, "SELinux restorecon failed for " + mDataDir);
+ }
}
public Intent configurationIntent() {
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 6bb7ac7..d1db230 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -835,6 +835,8 @@ public class ActionBarView extends AbsActionBarView {
(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle))) {
// Don't show while in expanded mode or with empty text
mTitleLayout.setVisibility(GONE);
+ } else {
+ mTitleLayout.setVisibility(VISIBLE);
}
}
@@ -1587,15 +1589,10 @@ public class ActionBarView extends AbsActionBarView {
mTitleLayout.setVisibility(VISIBLE);
}
}
- if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
- mTabScrollView.setVisibility(VISIBLE);
- }
- if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) {
- mSpinner.setVisibility(VISIBLE);
- }
- if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
- mCustomNavView.setVisibility(VISIBLE);
- }
+ if (mTabScrollView != null) mTabScrollView.setVisibility(VISIBLE);
+ if (mSpinner != null) mSpinner.setVisibility(VISIBLE);
+ if (mCustomNavView != null) mCustomNavView.setVisibility(VISIBLE);
+
mExpandedHomeLayout.setIcon(null);
mCurrentExpandedItem = null;
setHomeButtonEnabled(mWasHomeEnabled); // Set by expandItemActionView above
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 42d408d..f5eaf94 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -2,16 +2,16 @@
**
** Copyright 2006, The Android Open Source Project
**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
**
-** http://www.apache.org/licenses/LICENSE-2.0
+** http://www.apache.org/licenses/LICENSE-2.0
**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
+** 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.
*/
@@ -23,20 +23,17 @@
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
+#include <utils/String8.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
-#if defined(__arm__)
-extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize,
- size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
-
-extern "C" void free_malloc_leak_info(uint8_t* info);
-#endif
+extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize,
+ size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
-#define MAPS_FILE_SIZE 65 * 1024
+extern "C" void free_malloc_leak_info(uint8_t* info);
struct Header {
size_t mapSize;
@@ -48,96 +45,57 @@ struct Header {
namespace android {
+static void ReadFile(const char* path, String8& s) {
+ int fd = open(path, O_RDONLY);
+ if (fd != -1) {
+ char bytes[1024];
+ ssize_t byteCount;
+ while ((byteCount = TEMP_FAILURE_RETRY(read(fd, bytes, sizeof(bytes)))) > 0) {
+ s.append(bytes, byteCount);
+ }
+ close(fd);
+ }
+}
+
/*
- * Retrieve the native heap information and the info from /proc/<self>/maps,
+ * Retrieve the native heap information and the info from /proc/self/maps,
* copy them into a byte[] with a "struct Header" that holds data offsets,
* and return the array.
*/
-static jbyteArray getLeakInfo(JNIEnv *env, jobject clazz)
-{
-#if defined(__arm__)
- // get the info in /proc/[pid]/map
+static jbyteArray DdmHandleNativeHeap_getLeakInfo(JNIEnv* env, jobject) {
Header header;
memset(&header, 0, sizeof(header));
- pid_t pid = getpid();
-
- char path[FILENAME_MAX];
- sprintf(path, "/proc/%d/maps", pid);
-
- struct stat sb;
- int ret = stat(path, &sb);
-
- uint8_t* mapsFile = NULL;
- if (ret == 0) {
- mapsFile = (uint8_t*)malloc(MAPS_FILE_SIZE);
- int fd = open(path, O_RDONLY);
-
- if (mapsFile != NULL && fd != -1) {
- int amount = 0;
- do {
- uint8_t* ptr = mapsFile + header.mapSize;
- amount = read(fd, ptr, MAPS_FILE_SIZE);
- if (amount <= 0) {
- if (errno != EINTR)
- break;
- else
- continue;
- }
- header.mapSize += amount;
- } while (header.mapSize < MAPS_FILE_SIZE);
-
- ALOGD("**** read %d bytes from '%s'", (int) header.mapSize, path);
- }
- }
+ String8 maps;
+ ReadFile("/proc/self/maps", maps);
+ header.mapSize = maps.size();
uint8_t* allocBytes;
- get_malloc_leak_info(&allocBytes, &header.allocSize, &header.allocInfoSize,
- &header.totalMemory, &header.backtraceSize);
+ get_malloc_leak_info(&allocBytes, &header.allocSize, &header.allocInfoSize,
+ &header.totalMemory, &header.backtraceSize);
- jbyte* bytes = NULL;
- jbyte* ptr = NULL;
- jbyteArray array = env->NewByteArray(sizeof(Header) + header.mapSize + header.allocSize);
- if (array == NULL) {
- goto done;
- }
-
- bytes = env->GetByteArrayElements(array, NULL);
- ptr = bytes;
-
-// ALOGD("*** mapSize: %d allocSize: %d allocInfoSize: %d totalMemory: %d",
-// header.mapSize, header.allocSize, header.allocInfoSize, header.totalMemory);
+ ALOGD("*** mapSize: %d allocSize: %d allocInfoSize: %d totalMemory: %d",
+ header.mapSize, header.allocSize, header.allocInfoSize, header.totalMemory);
- memcpy(ptr, &header, sizeof(header));
- ptr += sizeof(header);
-
- if (header.mapSize > 0 && mapsFile != NULL) {
- memcpy(ptr, mapsFile, header.mapSize);
- ptr += header.mapSize;
+ jbyteArray array = env->NewByteArray(sizeof(Header) + header.mapSize + header.allocSize);
+ if (array != NULL) {
+ env->SetByteArrayRegion(array, 0,
+ sizeof(header), reinterpret_cast<jbyte*>(&header));
+ env->SetByteArrayRegion(array, sizeof(header),
+ maps.size(), reinterpret_cast<const jbyte*>(maps.string()));
+ env->SetByteArrayRegion(array, sizeof(header) + maps.size(),
+ header.allocSize, reinterpret_cast<jbyte*>(allocBytes));
}
-
- memcpy(ptr, allocBytes, header.allocSize);
- env->ReleaseByteArrayElements(array, bytes, 0);
-done:
- if (mapsFile != NULL) {
- free(mapsFile);
- }
- // free the info up!
free_malloc_leak_info(allocBytes);
-
return array;
-#else
- return NULL;
-#endif
}
static JNINativeMethod method_table[] = {
- { "getLeakInfo", "()[B", (void*)getLeakInfo },
+ { "getLeakInfo", "()[B", (void*) DdmHandleNativeHeap_getLeakInfo },
};
-int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env)
-{
+int register_android_ddm_DdmHandleNativeHeap(JNIEnv* env) {
return AndroidRuntime::registerNativeMethods(env, "android/ddm/DdmHandleNativeHeap", method_table, NELEM(method_table));
}
diff --git a/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_dark.9.png b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_dark.9.png
new file mode 100644
index 0000000..45450e4
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_light.9.png b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_light.9.png
new file mode 100644
index 0000000..b568989
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_default_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_dark.9.png b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_dark.9.png
new file mode 100644
index 0000000..e043458
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_light.9.png b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_light.9.png
new file mode 100644
index 0000000..f208c99
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_dark.9.png b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_dark.9.png
new file mode 100644
index 0000000..94eb994
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_light.9.png b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_light.9.png
new file mode 100644
index 0000000..1fee149
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-hdpi/btn_cab_done_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_dark.9.png b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_dark.9.png
new file mode 100644
index 0000000..abffc49
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_light.9.png b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_light.9.png
new file mode 100644
index 0000000..a369081
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_default_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_dark.9.png b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_dark.9.png
new file mode 100644
index 0000000..e33e964
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_light.9.png b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_light.9.png
new file mode 100644
index 0000000..0a845dd
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_dark.9.png b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_dark.9.png
new file mode 100644
index 0000000..74b0352
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_light.9.png b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_light.9.png
new file mode 100644
index 0000000..bfb4972
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-mdpi/btn_cab_done_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_dark.9.png b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_dark.9.png
new file mode 100644
index 0000000..d253dd4
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_light.9.png b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_light.9.png
new file mode 100644
index 0000000..65f9ec1
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_default_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_dark.9.png b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_dark.9.png
new file mode 100644
index 0000000..105da60
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_light.9.png b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_light.9.png
new file mode 100644
index 0000000..de53be7
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_dark.9.png b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_dark.9.png
new file mode 100644
index 0000000..3be0b0c
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_light.9.png b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_light.9.png
new file mode 100644
index 0000000..878c702
--- /dev/null
+++ b/core/res/res/drawable-ldrtl-xhdpi/btn_cab_done_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 630b1a5..88b5a2d 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -355,7 +355,7 @@
<string name="permlab_canRequestEnahncedWebAccessibility" msgid="1905232971331801453">"meminta aksesibilitas web yang disempurnakan"</string>
<string name="permdesc_canRequestEnahncedWebAccessibility" msgid="4500520989321729676">"Memungkinkan pemegang meminta pengaktifan penyempurnaan aksesibilitas web. Contohnya, memasang skrip agar konten aplikasi lebih mudah diakses."</string>
<string name="permlab_bindTextService" msgid="7358378401915287938">"mengikat ke layanan SMS"</string>
- <string name="permdesc_bindTextService" msgid="8151968910973998670">"Mengizinkan pemegang mengikat antarmuka tingkat tinggi dari suatu layanan teks (mis. SpellCheckerService). Tidak pernah diperlukan oleh apl normal."</string>
+ <string name="permdesc_bindTextService" msgid="8151968910973998670">"Mengizinkan pemegang mengikat antarmuka tingkat tinggi dari suatu layanan teks (misal: SpellCheckerService). Tidak pernah diperlukan oleh apl normal."</string>
<string name="permlab_bindVpnService" msgid="4708596021161473255">"mengikat ke layanan VPN"</string>
<string name="permdesc_bindVpnService" msgid="2067845564581693905">"Mengizinkan pemegang mengikat antarmuka tingkat tinggi dari suatu layanan Vpn. Tidak pernah diperlukan oleh apl normal."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"mengikat ke wallpaper"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 58e98a3..04ee91e 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -641,7 +641,7 @@
<string name="policydesc_expirePassword" msgid="1729725226314691591">"Dhibiti ni mara ngapi nenosiri la kufunga skrini linafaa libadilishwe."</string>
<string name="policylab_encryptedStorage" msgid="8901326199909132915">"Weka msimbo fiche wa hifadhi"</string>
<string name="policydesc_encryptedStorage" msgid="2637732115325316992">"Inahitaji kwamba data ya programu iliyohifadhiwa iwe na msimbo fiche."</string>
- <string name="policylab_disableCamera" msgid="6395301023152297826">"Lemaza kamera"</string>
+ <string name="policylab_disableCamera" msgid="6395301023152297826">"Zima kamera"</string>
<string name="policydesc_disableCamera" msgid="2306349042834754597">"Zuia matumizi yote ya kamera za kifaa."</string>
<string name="policylab_disableKeyguardFeatures" msgid="266329104542638802">"Lemaza vipengele kwenye kilinzi cha kitufe."</string>
<string name="policydesc_disableKeyguardFeatures" msgid="3467082272186534614">"Inazuia matumizi ya baadhi ya vipengele kwenye kilinzi cha kitufe."</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index badd3d7..5282df3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2709,6 +2709,16 @@
below and above child items.) The height of this will be the same as
the height of the normal list item divider. -->
<attr name="childDivider" format="reference|color" />
+ <!-- The start bound for an item's indicator. To specify a start bound specific to children,
+ use childIndicatorStart. -->
+ <attr name="indicatorStart" format="dimension" />
+ <!-- The end bound for an item's indicator. To specify a right bound specific to children,
+ use childIndicatorEnd. -->
+ <attr name="indicatorEnd" format="dimension" />
+ <!-- The start bound for a child's indicator. -->
+ <attr name="childIndicatorStart" format="dimension" />
+ <!-- The end bound for a child's indicator. -->
+ <attr name="childIndicatorEnd" format="dimension" />
</declare-styleable>
<declare-styleable name="Gallery">
<attr name="gravity" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9f810af..489a947 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2037,6 +2037,11 @@
<public type="attr" name="mirrorForRtl" />
<public type="attr" name="windowOverscan" />
<public type="attr" name="requiredForAllUsers" />
+ <public type="attr" name="indicatorStart" />
+ <public type="attr" name="indicatorEnd" />
+ <public type="attr" name="childIndicatorStart" />
+ <public type="attr" name="childIndicatorEnd" />
+
<public type="style" name="Theme.NoTitleBar.Overscan" />
<public type="style" name="Theme.Light.NoTitleBar.Overscan" />
<public type="style" name="Theme.Black.NoTitleBar.Overscan" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a2d4570..9e10661 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3202,7 +3202,8 @@
<string name="wifi_p2p_enter_pin_message">Type the required PIN: </string>
<string name="wifi_p2p_show_pin_message">PIN: </string>
- <string name="wifi_p2p_frequency_conflict_message">The phone will temporarily disconnect from Wi-Fi while it\'s connected to <xliff:g id="device_name">%1$s</xliff:g></string>
+ <string name="wifi_p2p_frequency_conflict_message" product="tablet">The tablet will temporarily disconnect from Wi-Fi while it\'s connected to <xliff:g id="device_name">%1$s</xliff:g></string>
+ <string name="wifi_p2p_frequency_conflict_message" product="default">The phone will temporarily disconnect from Wi-Fi while it\'s connected to <xliff:g id="device_name">%1$s</xliff:g></string>
<!-- Name of the dialog that lets the user choose an accented character to insert -->
<string name="select_character">Insert character</string>
diff --git a/docs/html/guide/topics/providers/calendar-provider.jd b/docs/html/guide/topics/providers/calendar-provider.jd
index f53b062..5adc68c 100644
--- a/docs/html/guide/topics/providers/calendar-provider.jd
+++ b/docs/html/guide/topics/providers/calendar-provider.jd
@@ -605,7 +605,7 @@ ContentValues values = new ContentValues();
Uri updateUri = null;
// The new title for the event
values.put(Events.TITLE, &quot;Kickboxing&quot;);
-myUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
int rows = getContentResolver().update(updateUri, values, null, null);
Log.i(DEBUG_TAG, &quot;Rows updated: &quot; + rows); </pre>
diff --git a/docs/html/training/contacts-provider/ContactsList.zip b/docs/html/training/contacts-provider/ContactsList.zip
new file mode 100644
index 0000000..d2a5cfb
--- /dev/null
+++ b/docs/html/training/contacts-provider/ContactsList.zip
Binary files differ
diff --git a/docs/html/training/contacts-provider/display-contact-badge.jd b/docs/html/training/contacts-provider/display-contact-badge.jd
new file mode 100644
index 0000000..f08935d
--- /dev/null
+++ b/docs/html/training/contacts-provider/display-contact-badge.jd
@@ -0,0 +1,635 @@
+page.title=Displaying the Quick Contact Badge
+
+trainingnavtop=true
+@jd:body
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li>
+ <a href="#AddView">Add a QuickContactBadge View</a>
+ </li>
+ <li>
+ <a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a>
+ </li>
+ <li>
+ <a href="#ListView">
+ Add a QuickContactBadge to a ListView
+ </a>
+ </li>
+</ol>
+
+<!-- other docs (NOT javadocs) -->
+<h2>You should also read</h2>
+<ul>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics
+ </a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/contacts-provider.html">
+ Contacts Provider
+ </a>
+ </li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button">
+ Download the sample
+ </a>
+ <p class="filename">ContactsList.zip</p>
+</div>
+
+</div>
+</div>
+<p>
+ This lesson shows you how to add a {@link android.widget.QuickContactBadge} to your UI
+ and how to bind data to it. A {@link android.widget.QuickContactBadge} is a widget that
+ initially appears as a thumbnail image. Although you can use any {@link android.graphics.Bitmap}
+ for the thumbnail image, you usually use a {@link android.graphics.Bitmap} decoded from the
+ contact's photo thumbnail image.
+</p>
+<p>
+ The small image acts as a control; when users click on the image, the
+ {@link android.widget.QuickContactBadge} expands into a dialog containing the following:
+</p>
+<dl>
+ <dt>A large image</dt>
+ <dd>
+ The large image associated with the contact, or no image is available, a placeholder
+ graphic.
+ </dd>
+ <dt>
+ App icons
+ </dt>
+ <dd>
+ An app icon for each piece of detail data that can be handled by a built-in app. For
+ example, if the contact's details include one or more email addresses, an email icon
+ appears. When users click the icon, all of the contact's email addresses appear. When users
+ click one of the addresses, the email app displays a screen for composing a message to the
+ selected email address.
+ </dd>
+</dl>
+<p>
+ The {@link android.widget.QuickContactBadge} view provides instant access to a contact's
+ details, as well as a fast way of communicating with the contact. Users don't have to look up
+ a contact, find and copy information, and then paste it into the appropriate app. Instead, they
+ can click on the {@link android.widget.QuickContactBadge}, choose the communication method they
+ want to use, and send the information for that method directly to the appropriate app.
+</p>
+<h2 id="AddView">Add a QuickContactBadge View</h2>
+<p>
+ To add a {@link android.widget.QuickContactBadge}, insert a
+ <code>&lt;QuickContactBadge&gt;</code> element in your layout. For example:
+</p>
+<pre>
+&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"&gt;
+...
+ &lt;QuickContactBadge
+ android:id=&#64;+id/quickbadge
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:scaleType="centerCrop"/&gt;
+ ...
+&lt;/RelativeLayout&gt;
+</pre>
+<h2 id="">Retrieve provider data</h2>
+<p>
+ To display a contact in the {@link android.widget.QuickContactBadge}, you need a content URI
+ for the contact and a {@link android.graphics.Bitmap} for the small image. You generate
+ both the content URI and the {@link android.graphics.Bitmap} from columns retrieved from the
+ Contacts Provider. Specify these columns as part of the projection you use to load data into
+ your {@link android.database.Cursor}.
+</p>
+<p>
+ For Android 3.0 (API level 11) and later, include the following columns in your projection:</p>
+<ul>
+ <li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li>
+ <li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li>
+ <li>
+ {@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI
+ Contacts.PHOTO_THUMBNAIL_URI}
+ </li>
+</ul>
+<p>
+ For Android 2.3.3 (API level 10) and earlier, use the following columns:
+</p>
+<ul>
+ <li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li>
+ <li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li>
+</ul>
+<p>
+ The remainder of this lesson assumes that you've already loaded a
+ {@link android.database.Cursor} that contains these columns as well as others you may have
+ chosen. To learn how to retrieve this columns in a {@link android.database.Cursor}, read the
+ lesson <a href="retrieve-names.html">Retrieving a List of Contacts</a>.
+</p>
+<h2 id="SetURIThumbnail">Set the Contact URI and Thumbnail</h2>
+<p>
+ Once you have the necessary columns, you can bind data to the
+ {@link android.widget.QuickContactBadge}.
+</p>
+<h3>Set the Contact URI</h3>
+<p>
+ To set the content URI for the contact, call
+ {@link android.provider.ContactsContract.Contacts#getLookupUri getLookupUri(id,lookupKey)} to
+ get a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, then
+ call {@link android.widget.QuickContactBadge#assignContactUri assignContactUri()} to set the
+ contact. For example:
+</p>
+<pre>
+ // The Cursor that contains contact rows
+ Cursor mCursor;
+ // The index of the _ID column in the Cursor
+ int mIdColumn;
+ // The index of the LOOKUP_KEY column in the Cursor
+ int mLookupKeyColumn;
+ // A content URI for the desired contact
+ Uri mContactUri;
+ // A handle to the QuickContactBadge view
+ QuickContactBadge mBadge;
+ ...
+ mBadge = (QuickContactBadge) findViewById(R.id.quickbadge);
+ /*
+ * Insert code here to move to the desired cursor row
+ */
+ // Gets the _ID column index
+ mIdColumn = mCursor.getColumnIndex(Contacts._ID);
+ // Gets the LOOKUP_KEY index
+ mLookupKeyColumn = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
+ // Gets a content URI for the contact
+ mContactUri =
+ Contacts.getLookupUri(
+ Cursor.getLong(mIdColumn),
+ Cursor.getString(mLookupKeyColumn)
+ );
+ mBadge.assignContactUri(mContactUri);
+</pre>
+<p>
+ When users click the {@link android.widget.QuickContactBadge} icon, the contact's
+ details automatically appear in the dialog.
+</p>
+<h3>Set the photo thumbnail</h3>
+<p>
+ Setting the contact URI for the {@link android.widget.QuickContactBadge} does not automatically
+ load the contact's thumbnail photo. To load the photo, get a URI for the photo from the
+ contact's {@link android.database.Cursor} row, use it to open the file containing the compressed
+ thumbnail photo, and read the file into a {@link android.graphics.Bitmap}.
+</p>
+<p class="note">
+ <strong>Note:</strong> The
+ {@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI} column isn't available
+ in platform versions prior to 3.0. For those versions, you must retrieve the URI
+ from the {@link android.provider.ContactsContract.Contacts.Photo Contacts.Photo} subtable.
+</p>
+<p>
+ First, set up variables for accessing the {@link android.database.Cursor} containing the
+ {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and
+ {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} columns, as
+ described previously:
+</p>
+<pre>
+ // The column in which to find the thumbnail ID
+ int mThumbnailColumn;
+ /*
+ * The thumbnail URI, expressed as a String.
+ * Contacts Provider stores URIs as String values.
+ */
+ String mThumbnailUri;
+ ...
+ /*
+ * Gets the photo thumbnail column index if
+ * platform version &gt;= Honeycomb
+ */
+ if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) {
+ mThumbnailColumn =
+ mCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);
+ // Otherwise, sets the thumbnail column to the _ID column
+ } else {
+ mThumbnailColumn = mIdColumn;
+ }
+ /*
+ * Assuming the current Cursor position is the contact you want,
+ * gets the thumbnail ID
+ */
+ mThumbnailUri = Cursor.getString(mThumbnailColumn);
+ ...
+</pre>
+<p>
+ Define a method that takes photo-related data for the contact and dimensions for the
+ destination view, and returns the properly-sized thumbnail in a
+ {@link android.graphics.Bitmap}. Start by constructing a URI that points to the
+ thumbnail:
+<p>
+<pre>
+ /**
+ * Load a contact photo thumbnail and return it as a Bitmap,
+ * resizing the image to the provided image dimensions as needed.
+ * @param photoData photo ID Prior to Honeycomb, the contact's _ID value.
+ * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI.
+ * @return A thumbnail Bitmap, sized to the provided width and height.
+ * Returns null if the thumbnail is not found.
+ */
+ private Bitmap loadContactPhotoThumbnail(String photoData) {
+ // Creates an asset file descriptor for the thumbnail file.
+ AssetFileDescriptor afd = null;
+ // try-catch block for file not found
+ try {
+ // Creates a holder for the URI.
+ Uri thumbUri;
+ // If Android 3.0 or later
+ if (Build.VERSION.SDK_INT
+ &gt;=
+ Build.VERSION_CODES.HONEYCOMB) {
+ // Sets the URI from the incoming PHOTO_THUMBNAIL_URI
+ thumbUri = Uri.parse(photoData);
+ } else {
+ // Prior to Android 3.0, constructs a photo Uri using _ID
+ /*
+ * Creates a contact URI from the Contacts content URI
+ * incoming photoData (_ID)
+ */
+ final Uri contactUri = Uri.withAppendedPath(
+ Contacts.CONTENT_URI, photoData);
+ /*
+ * Creates a photo URI by appending the content URI of
+ * Contacts.Photo.
+ */
+ thumbUri =
+ Uri.withAppendedPath(
+ contactUri, Photo.CONTENT_DIRECTORY);
+ }
+
+ /*
+ * Retrieves an AssetFileDescriptor object for the thumbnail
+ * URI
+ * using ContentResolver.openAssetFileDescriptor
+ */
+ afd = getActivity().getContentResolver().
+ openAssetFileDescriptor(thumbUri, "r");
+ /*
+ * Gets a file descriptor from the asset file descriptor.
+ * This object can be used across processes.
+ */
+ FileDescriptor fileDescriptor = afd.getFileDescriptor();
+ // Decode the photo file and return the result as a Bitmap
+ // If the file descriptor is valid
+ if (fileDescriptor != null) {
+ // Decodes the bitmap
+ return BitmapFactory.decodeFileDescriptor(
+ fileDescriptor, null, null);
+ }
+ // If the file isn't found
+ } catch (FileNotFoundException e) {
+ /*
+ * Handle file not found errors
+ */
+ }
+ // In all cases, close the asset file descriptor
+ } finally {
+ if (afd != null) {
+ try {
+ afd.close();
+ } catch (IOException e) {}
+ }
+ }
+ return null;
+ }
+</pre>
+<p>
+ Call the <code>loadContactPhotoThumbnail()</code> method in your code to get the
+ thumbnail {@link android.graphics.Bitmap}, and use the result to set the photo thumbnail in
+ your {@link android.widget.QuickContactBadge}:
+</p>
+<pre>
+ ...
+ /*
+ * Decodes the thumbnail file to a Bitmap.
+ */
+ Bitmap mThumbnail =
+ loadContactPhotoThumbnail(mThumbnailUri);
+ /*
+ * Sets the image in the QuickContactBadge
+ * QuickContactBadge inherits from ImageView, so
+ */
+ mBadge.setImageBitmap(mThumbnail);
+</pre>
+<h2 id="ListView">Add a QuickContactBadge to a ListView</h2>
+<p>
+ A {@link android.widget.QuickContactBadge} is a useful addition to a
+ {@link android.widget.ListView} that displays a list of contacts. Use the
+ {@link android.widget.QuickContactBadge} to display a thumbnail photo for each contact; when
+ users click the thumbnail, the {@link android.widget.QuickContactBadge} dialog appears.
+</p>
+<h3>Add the QuickContactBadge element</h3>
+<p>
+ To start, add a {@link android.widget.QuickContactBadge} view element to your item layout
+ For example, if you want to display a {@link android.widget.QuickContactBadge} and a name for
+ each contact you retrieve, put the following XML into a layout file:
+</p>
+<pre>
+&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"&gt;
+ &lt;QuickContactBadge
+ android:id="&#64;+id/quickcontact"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:scaleType="centerCrop"/&gt;
+ &lt;TextView android:id="&#64;+id/displayname"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="&#64;+id/quickcontact"
+ android:gravity="center_vertical"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"/&gt;
+&lt;/RelativeLayout&gt;
+</pre>
+<p>
+ In the following sections, this file is referred to as <code>contact_item_layout.xml</code>.
+</p>
+<h3>Set up a custom CursorAdapter</h3>
+<p>
+ To bind a {@link android.support.v4.widget.CursorAdapter} to a {@link android.widget.ListView}
+ containing a {@link android.widget.QuickContactBadge}, define a custom adapter that
+ extends {@link android.support.v4.widget.CursorAdapter}. This approach allows you to process the
+ data in the {@link android.database.Cursor} before you bind it to the
+ {@link android.widget.QuickContactBadge}. This approach also allows you to bind multiple
+ {@link android.database.Cursor} columns to the {@link android.widget.QuickContactBadge}. Neither
+ of these operations is possible in a regular {@link android.support.v4.widget.CursorAdapter}.
+</p>
+<p>
+ The subclass of {@link android.support.v4.widget.CursorAdapter} that you define must
+ override the following methods:
+</p>
+<dl>
+ <dt>{@link android.support.v4.widget.CursorAdapter#newView CursorAdapter.newView()}</dt>
+ <dd>
+ Inflates a new {@link android.view.View} object to hold the item layout. In the override
+ of this method, store handles to the child {@link android.view.View} objects of the layout,
+ including the child {@link android.widget.QuickContactBadge}. By taking this approach, you
+ avoid having to get handles to the child {@link android.view.View} objects each time you
+ inflate a new layout.
+ <p>
+ You must override this method so you can get handles to the individual child
+ {@link android.view.View} objects. This technique allows you to control their binding in
+ {@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}.
+ </p>
+ </dd>
+ <dt>{@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}</dt>
+ <dd>
+ Moves data from the current {@link android.database.Cursor} row to the child
+ {@link android.view.View} objects of the item layout. You must override this method so
+ you can bind both the contact's URI and thumbnail to the
+ {@link android.widget.QuickContactBadge}. The default implementation only allows a 1-to-1
+ mapping between a column and a {@link android.view.View}
+ </dd>
+</dl>
+<p>
+ The following code snippet contains an example of a custom subclass of
+ {@link android.support.v4.widget.CursorAdapter}:
+</p>
+<h3>Define the custom list adapter</h3>
+<p>
+ Define the subclass of {@link android.support.v4.widget.CursorAdapter} including its
+ constructor, and override
+ {@link android.support.v4.widget.CursorAdapter#newView newView()} and
+ {@link android.support.v4.widget.CursorAdapter#bindView bindView()}:
+</p>
+<pre>
+ /**
+ *
+ *
+ */
+ private class ContactsAdapter extends CursorAdapter {
+ private LayoutInflater mInflater;
+ ...
+ public ContactsAdapter(Context context) {
+ super(context, null, 0);
+
+ /*
+ * Gets an inflater that can instantiate
+ * the ListView layout from the file.
+ */
+ mInflater = LayoutInflater.from(context);
+ ...
+ }
+ ...
+ /**
+ * Defines a class that hold resource IDs of each item layout
+ * row to prevent having to look them up each time data is
+ * bound to a row.
+ */
+ private class ViewHolder {
+ TextView displayname;
+ QuickContactBadge quickcontact;
+ }
+ ..
+ &#64;Override
+ public View newView(
+ Context context,
+ Cursor cursor,
+ ViewGroup viewGroup) {
+ /* Inflates the item layout. Stores resource IDs in a
+ * in a ViewHolder class to prevent having to look
+ * them up each time bindView() is called.
+ */
+ final View itemView =
+ mInflater.inflate(
+ R.layout.contact_list_layout,
+ viewGroup,
+ false
+ );
+ final ViewHolder holder = new ViewHolder();
+ holder.displayname =
+ (TextView) view.findViewById(R.id.displayname);
+ holder.quickcontact =
+ (QuickContactBadge)
+ view.findViewById(R.id.quickcontact);
+ view.setTag(holder);
+ return view;
+ }
+ ...
+ &#64;Override
+ public void bindView(
+ View view,
+ Context context,
+ Cursor cursor) {
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ final String photoData =
+ cursor.getString(mPhotoDataIndex);
+ final String displayName =
+ cursor.getString(mDisplayNameIndex);
+ ...
+ // Sets the display name in the layout
+ holder.displayname = cursor.getString(mDisplayNameIndex);
+ ...
+ /*
+ * Generates a contact URI for the QuickContactBadge.
+ */
+ final Uri contactUri = Contacts.getLookupUri(
+ cursor.getLong(mIdIndex),
+ cursor.getString(mLookupKeyIndex));
+ holder.quickcontact.assignContactUri(contactUri);
+ String photoData = cursor.getString(mPhotoDataIndex);
+ /*
+ * Decodes the thumbnail file to a Bitmap.
+ * The method loadContactPhotoThumbnail() is defined
+ * in the section "Set the Contact URI and Thumbnail"
+ */
+ Bitmap thumbnailBitmap =
+ loadContactPhotoThumbnail(photoData);
+ /*
+ * Sets the image in the QuickContactBadge
+ * QuickContactBadge inherits from ImageView
+ */
+ holder.quickcontact.setImageBitmap(thumbnailBitmap);
+ }
+</pre>
+
+<h3>Set up variables</h3>
+<p>
+ In your code, set up variables, including a {@link android.database.Cursor} projection that
+ includes the necessary columns.
+</p>
+<p class="note">
+ <strong>Note:</strong> The following code snippets use the method
+ <code>loadContactPhotoThumbnail()</code>, which is defined in the section
+ <a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a>
+</p>
+<p>
+ For example:
+</p>
+<pre>
+public class ContactsFragment extends Fragment implements
+ LoaderManager.LoaderCallbacks&lt;Cursor&gt; {
+...
+ // Defines a ListView
+ private ListView mListView;
+ // Defines a ContactsAdapter
+ private ContactsAdapter mAdapter;
+ ...
+ // Defines a Cursor to contain the retrieved data
+ private Cursor mCursor;
+ /*
+ * Defines a projection based on platform version. This ensures
+ * that you retrieve the correct columns.
+ */
+ private static final String[] PROJECTION =
+ {
+ Contacts._ID,
+ Contacts.LOOKUP_KEY,
+ (Build.VERSION.SDK_INT &gt;=
+ Build.VERSION_CODES.HONEYCOMB) ?
+ Contacts.DISPLAY_NAME_PRIMARY :
+ Contacts.DISPLAY_NAME
+ (Build.VERSION.SDK_INT &gt;=
+ Build.VERSION_CODES.HONEYCOMB) ?
+ Contacts.PHOTO_THUMBNAIL_ID :
+ /*
+ * Although it's not necessary to include the
+ * column twice, this keeps the number of
+ * columns the same regardless of version
+ */
+ Contacts_ID
+ ...
+ };
+ /*
+ * As a shortcut, defines constants for the
+ * column indexes in the Cursor. The index is
+ * 0-based and always matches the column order
+ * in the projection.
+ */
+ // Column index of the _ID column
+ private int mIdIndex = 0;
+ // Column index of the LOOKUP_KEY column
+ private int mLookupKeyIndex = 1;
+ // Column index of the display name column
+ private int mDisplayNameIndex = 3;
+ /*
+ * Column index of the photo data column.
+ * It's PHOTO_THUMBNAIL_URI for Honeycomb and later,
+ * and _ID for previous versions.
+ */
+ private int mPhotoDataIndex =
+ Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB ?
+ 3 :
+ 0;
+ ...
+</pre>
+<h3>Set up the ListView</h3>
+<p>
+ In {@link android.support.v4.app.Fragment#onCreate Fragment.onCreate()}, instantiate the custom
+ cursor adapter and get a handle to the {@link android.widget.ListView}:
+</p>
+<pre>
+ &#64;Override
+ public void onCreate(Bundle savedInstanceState) {
+ ...
+ /*
+ * Instantiates the subclass of
+ * CursorAdapter
+ */
+ ContactsAdapter mContactsAdapter =
+ new ContactsAdapter(getActivity());
+ /*
+ * Gets a handle to the ListView in the file
+ * contact_list_layout.xml
+ */
+ mListView = (ListView) findViewById(R.layout.contact_list_layout);
+ ...
+ }
+ ...
+</pre>
+<p>
+ In {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, bind the
+ <code>ContactsAdapter</code> to the {@link android.widget.ListView}:
+</p>
+<pre>
+ &#64;Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ ...
+ // Sets up the adapter for the ListView
+ mListView.setAdapter(mAdapter);
+ ...
+ }
+ ...
+</pre>
+<p>
+ When you get back a {@link android.database.Cursor} containing the contacts data, usually in
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()},
+ call {@link android.support.v4.widget.CursorAdapter#swapCursor swapCursor()} to move the
+ {@link android.database.Cursor} data to the {@link android.widget.ListView}. This displays the
+ {@link android.widget.QuickContactBadge} for each entry in the list of contacts:
+</p>
+<pre>
+ public void onLoadFinished(Loader&lt;Cursor&gt; loader, Cursor cursor) {
+ // When the loader has completed, swap the cursor into the adapter.
+ mContactsAdapter.swapCursor(cursor);
+ }
+</pre>
+<p>
+ When you bind a {@link android.database.Cursor} to a
+ {@link android.widget.ListView} with a {@link android.support.v4.widget.CursorAdapter}
+ (or subclass), and you use a {@link android.support.v4.content.CursorLoader} to load the
+ {@link android.database.Cursor}, always clear references to the {@link android.database.Cursor}
+ in your implementation of
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()}.
+ For example:
+</p>
+<pre>
+ &#64;Override
+ public void onLoaderReset(Loader&lt;Cursor&gt; loader) {
+ // Removes remaining reference to the previous Cursor
+ mContactsAdapter.swapCursor(null);
+ }
+</pre>
diff --git a/docs/html/training/contacts-provider/index.jd b/docs/html/training/contacts-provider/index.jd
new file mode 100644
index 0000000..f380d95
--- /dev/null
+++ b/docs/html/training/contacts-provider/index.jd
@@ -0,0 +1,97 @@
+page.title=Accessing Contacts Data
+
+trainingnavtop=true
+startpage=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+<h2>Dependencies and prerequisites</h2>
+<ul>
+ <li>Android 2.0 (API Level 5) or higher</li>
+ <li>Experience in using {@link android.content.Intent} objects</li>
+ <li>Experience in using content providers</li>
+</ul>
+
+<!-- related docs (NOT javadocs) -->
+<h2>You should also read</h2>
+<ul>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/contacts-provider.html">
+ Contacts Provider</a>
+ </li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button">
+ Download the sample
+ </a>
+ <p class="filename">ContactsList.zip</p>
+</div>
+
+</div>
+</div>
+
+<p>
+ The <a href="{@docRoot}guide/topics/providers/contacts-provider.html">Contacts Provider</a> is
+ the central repository of the user's contacts information, including data from contacts apps and
+ social networking apps. In your apps, you can access Contacts Provider information directly by
+ calling {@link android.content.ContentResolver} methods or by sending intents to a contacts app.
+</p>
+<p>
+ This class focuses on retrieving lists of contacts, displaying the details for a particular
+ contact, and modifying contacts using intents. The basic techniques described
+ here can be extended to perform more complex tasks. In addition, this class helps you
+ understand the overall structure and operation of the
+ <a href="{@docRoot}guide/topics/providers/contacts-provider.html">Contacts Provider</a>.
+</p>
+<h2>Lessons</h2>
+
+<dl>
+ <dt>
+ <b><a href="retrieve-names.html">Retrieving a List of Contacts</a></b>
+ </dt>
+ <dd>
+ Learn how to retrieve a list of contacts for which the data matches all or part of a search
+ string, using the following techniques:
+ <ul>
+ <li>Match by contact name</li>
+ <li>Match any type of contact data</li>
+ <li>Match a specific type of contact data, such as a phone number</li>
+ </ul>
+ </dd>
+ <dt>
+ <b><a href="retrieve-details.html">Retrieving Details for a Contact</a></b>
+ </dt>
+ <dd>
+ Learn how to retrieve the details for a single contact. A contact's details are data
+ such as phone numbers and email addresses. You can retrieve all details, or you can
+ retrieve details of a specific type, such as all email addresses.
+ </dd>
+ <dt>
+ <b><a href="modify-data.html">Modifying Contacts Using Intents</a></b>
+ </dt>
+ <dd>
+ Learn how to modify a contact by sending an intent to the People app.
+ </dd>
+ <dt>
+ <b>
+ <a href="display-contact-badge.html">Displaying the Quick Contact Badge</a>
+ </b>
+ </dt>
+ <dd>
+ Learn how to display the {@link android.widget.QuickContactBadge} widget. When the user
+ clicks the contact badge widget, a dialog opens that displays the contact's details and
+ action buttons for apps that can handle the details. For example, if the contact has an
+ email address, the dialog displays an action button for the default email app.
+ </dd>
+</dl>
diff --git a/docs/html/training/contacts-provider/modify-data.jd b/docs/html/training/contacts-provider/modify-data.jd
new file mode 100644
index 0000000..64853ef
--- /dev/null
+++ b/docs/html/training/contacts-provider/modify-data.jd
@@ -0,0 +1,305 @@
+page.title=Modifying Contacts Using Intents
+trainingnavtop=true
+@jd:body
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#InsertContact">Insert a New Contact Using an Intent</a></li>
+ <li><a href="#EditContact">Edit an Existing Contact Using an Intent</a></li>
+ <li><a href="#InsertEdit">Let Users Choose to Insert or Edit Using an Intent</a>
+</ol>
+<h2>You should also read</h2>
+<ul>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics
+ </a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/contacts-provider.html">
+ Contacts Provider
+ </a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a>
+ </li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button">
+ Download the sample
+ </a>
+ <p class="filename">ContactsList.zip</p>
+</div>
+
+</div>
+</div>
+<p>
+ This lesson shows you how to use an {@link android.content.Intent} to insert a new contact or
+ modify a contact's data. Instead of accessing the Contacts Provider directly, an
+ {@link android.content.Intent} starts the contacts app, which runs the appropriate
+ {@link android.app.Activity}. For the modification actions described in this lesson,
+ if you send extended data in the {@link android.content.Intent} it's entered into the UI of the
+ {@link android.app.Activity} that is started.
+</p>
+<p>
+ Using an {@link android.content.Intent} to insert or update a single contact is the preferred
+ way of modifying the Contacts Provider, for the following reasons:
+</p>
+<ul>
+ <li>It saves you the time and and effort of developing your own UI and code.</li>
+ <li>
+ It avoids introducing errors caused by modifications that don't follow the
+ Contacts Provider's rules.
+ </li>
+ <li>
+ It reduces the number of permissions you need to request. Your app doesn't need permission
+ to write to the Contacts Provider, because it delegates modifications to the contacts app,
+ which already has that permission.
+ </li>
+</ul>
+<h2 id="InsertContact">Insert a New Contact Using an Intent</h2>
+<p>
+ You often want to allow the user to insert a new contact when your app receives new data. For
+ example, a restaurant review app can allow users to add the restaurant as a contact as they're
+ reviewing it. To do this using an intent, create the intent using as much data as you have
+ available, and then send the intent to the contacts app.
+</p>
+<p>
+ Inserting a contact using the contacts app inserts a new <em>raw</em> contact into the Contacts
+ Provider's {@link android.provider.ContactsContract.RawContacts} table. If necessary,
+ the contacts app prompts users for the account type and account to use when creating the raw
+ contact. The contacts app also notifies users if the raw contact already exists. Users then have
+ option of canceling the insertion, in which case no contact is created. To learn
+ more about raw contacts, see the
+ <a href="{@docRoot}guide/topics/providers/contacts-provider.html">Contacts Provider</a>
+ API guide.
+</p>
+
+<h3>Create an Intent</h3>
+<p>
+ To start, create a new {@link android.content.Intent} object with the action
+ {@link android.provider.ContactsContract.Intents.Insert#ACTION Intents.Insert.ACTION}.
+ Set the MIME type to {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE
+ RawContacts.CONTENT_TYPE}. For example:
+</p>
+<pre>
+...
+// Creates a new Intent to insert a contact
+Intent intent = new Intent(Intents.Insert.ACTION);
+// Sets the MIME type to match the Contacts Provider
+intent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+</pre>
+<p>
+ If you already have details for the contact, such as a phone number or email address, you can
+ insert them into the intent as extended data. For a key value, use the appropriate constant from
+ {@link android.provider.ContactsContract.Intents.Insert Intents.Insert}. The contacts app
+ displays the data in its insert screen, allowing users to make further edits and additions.
+</p>
+<pre>
+/* Assumes EditText fields in your UI contain an email address
+ * and a phone number.
+ *
+ */
+private EditText mEmailAddress = (EditText) findViewById(R.id.email);
+private EditText mPhoneNumber = (EditText) findViewById(R.id.phone);
+...
+/*
+ * Inserts new data into the Intent. This data is passed to the
+ * contacts app's Insert screen
+ */
+// Inserts an email address
+intent.putExtra(Intents.Insert.EMAIL, mEmailAddress.getText())
+/*
+ * In this example, sets the email type to be a work email.
+ * You can set other email types as necessary.
+ */
+ .putExtra(Intents.Insert.EMAIL_TYPE, CommonDataKinds.Email.TYPE_WORK)
+// Inserts a phone number
+ .putExtra(Intents.Insert.PHONE, mPhoneNumber.getText())
+/*
+ * In this example, sets the phone type to be a work phone.
+ * You can set other phone types as necessary.
+ */
+ .putExtra(Intents.Insert.PHONE_TYPE, Phone.TYPE_WORK);
+
+</pre>
+<p>
+ Once you've created the {@link android.content.Intent}, send it by calling
+ {@link android.support.v4.app.Fragment#startActivity startActivity()}.
+</p>
+<pre>
+ /* Sends the Intent
+ */
+ startActivity(intent);
+</pre>
+<p>
+ This call opens a screen in the contacts app that allows users to enter a new contact. The
+ account type and account name for the contact is listed at the top of the screen. Once users
+ enter the data and click <i>Done</i>, the contacts app's contact list appears. Users return to
+ your app by clicking <i>Back</i>.
+</p>
+<h2 id="EditContact">Edit an Existing Contact Using an Intent</h2>
+<p>
+ Editing an existing contact using an {@link android.content.Intent} is useful if the user
+ has already chosen a contact of interest. For example, an app that finds contacts that have
+ postal addresses but lack a postal code could give users the option of looking up the code and
+ then adding it to the contact.
+</p>
+<p>
+ To edit an existing contact using an intent, use a procedure similar to
+ inserting a contact. Create an intent as described in the section
+ <a href="#InsertContact">Insert a New Contact Using an Intent</a>, but add the contact's
+ {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI
+ Contacts.CONTENT_LOOKUP_URI} and the MIME type
+ {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE
+ Contacts.CONTENT_ITEM_TYPE} to the intent. If you want to edit the contact with details you
+ already have, you can put them in the intent's extended data. Notice that some
+ name columns can't be edited using an intent; these columns are listed in the summary
+ section of the API reference for the class {@link android.provider.ContactsContract.Contacts}
+ under the heading "Update".
+</p>
+<p>
+ Finally, send the intent. In response, the contacts app displays an edit screen. When the user
+ finishes editing and saves the edits, the contacts app displays a contact list. When the user
+ clicks <i>Back</i>, your app is displayed.
+</p>
+<div class="sidebox-wrapper">
+<div class="sidebox">
+ <h2>Contacts Lookup Key</h2>
+ <p>
+ A contact's {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} value is
+ the identifier that you should use to retrieve a contact. It remains constant,
+ even if the provider changes the contact's row ID to handle internal operations.
+ </p>
+</div>
+</div>
+<h3>Create the Intent</h3>
+<p>
+ To edit a contact, call {@link android.content.Intent#Intent Intent(action)} to
+ create an intent with the action {@link android.content.Intent#ACTION_EDIT}. Call
+ {@link android.content.Intent#setDataAndType setDataAndType()} to set the data value for the
+ intent to the contact's {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI
+ Contacts.CONTENT_LOOKUP_URI} and the MIME type to
+ {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE
+ Contacts.CONTENT_ITEM_TYPE} MIME type; because a call to
+ {@link android.content.Intent#setType setType()} overwrites the current data value for the
+ {@link android.content.Intent}, you must set the data and the MIME type at the same time.
+</p>
+<p>
+ To get a contact's {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI
+ Contacts.CONTENT_LOOKUP_URI}, call
+ {@link android.provider.ContactsContract.Contacts#getLookupUri
+ Contacts.getLookupUri(id, lookupkey)} with the contact's
+ {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and
+ {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} values as
+ arguments.
+</p>
+<p>
+ The following snippet shows you how to create an intent:
+</p>
+<pre>
+ // The Cursor that contains the Contact row
+ public Cursor mCursor;
+ // The index of the lookup key column in the cursor
+ public int mLookupKeyIndex;
+ // The index of the contact's _ID value
+ public int mIdIndex;
+ // The lookup key from the Cursor
+ public String mCurrentLookupKey;
+ // The _ID value from the Cursor
+ public long mCurrentId;
+ // A content URI pointing to the contact
+ Uri mSelectedContactUri;
+ ...
+ /*
+ * Once the user has selected a contact to edit,
+ * this gets the contact's lookup key and _ID values from the
+ * cursor and creates the necessary URI.
+ */
+ // Gets the lookup key column index
+ mLookupKeyIndex = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
+ // Gets the lookup key value
+ mCurrentLookupKey = mCursor.getString(mLookupKeyIndex);
+ // Gets the _ID column index
+ mIdIndex = mCursor.getColumnIndex(Contacts._ID);
+ mCurrentId = mCursor.getLong(mIdIndex);
+ mSelectedContactUri =
+ Contacts.getLookupUri(mCurrentId, mCurrentLookupKey);
+ ...
+ // Creates a new Intent to edit a contact
+ Intent editIntent = new Intent(Intent.ACTION_EDIT);
+ /*
+ * Sets the contact URI to edit, and the data type that the
+ * Intent must match
+ */
+ editIntent.setDataAndType(mSelectedContactUri,Contacts.CONTENT_ITEM_TYPE);
+</pre>
+<h3>Add the navigation flag</h3>
+<p>
+ In Android 4.0 (API version 14) and later, a problem in the contacts app causes incorrect
+ navigation. When your app sends an edit intent to the contacts app, and users edit and save a
+ contact, when they click <i>Back</i> they see the contacts list screen. To navigate back to
+ your app, they have to click <i>Recents</i> and choose your app.
+</p>
+<p>
+ To work around this problem in Android 4.0.3 (API version 15) and later, add the extended
+ data key {@code finishActivityOnSaveCompleted} to the intent, with a value of {@code true}.
+ Android versions prior to Android 4.0 accept this key, but it has no effect. To set the
+ extended data, do the following:
+</p>
+<pre>
+ // Sets the special extended data for navigation
+ editIntent.putExtra("finishActivityOnSaveCompleted", true);
+</pre>
+<h3>Add other extended data</h3>
+<p>
+ To add additional extended data to the {@link android.content.Intent}, call
+ {@link android.content.Intent#putExtra putExtra()} as desired.
+ You can add extended data for common contact fields by using the key values specified in
+ {@link android.provider.ContactsContract.Intents.Insert Intents.Insert}. Remember that some
+ columns in the {@link android.provider.ContactsContract.Contacts} table can't be modified.
+ These columns are listed in the summary section of the API reference for the class
+ {@link android.provider.ContactsContract.Contacts} under the heading "Update".
+</p>
+
+<h3>Send the Intent</h3>
+<p>
+ Finally, send the intent you've constructed. For example:
+</p>
+<pre>
+ // Sends the Intent
+ startActivity(editIntent);
+</pre>
+<h2 id="InsertEdit">Let Users Choose to Insert or Edit Using an Intent</h2>
+<p>
+ You can allow users to choose whether to insert a contact or edit an existing one by sending
+ an {@link android.content.Intent} with the action
+ {@link android.content.Intent#ACTION_INSERT_OR_EDIT}. For example, an email client app could
+ allow users to add an incoming email address to a new contact, or add it as an additional
+ address for an existing contact. Set the MIME type for this intent to
+ {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE Contacts.CONTENT_ITEM_TYPE},
+ but don't set the data URI.
+</p>
+<p>
+ When you send this intent, the contacts app displays a list of contacts.
+ Users can either insert a new contact or pick an existing contact and edit it.
+ Any extended data fields you add to the intent populates the screen that appears. You can use
+ any of the key values specified in {@link android.provider.ContactsContract.Intents.Insert
+ Intents.Insert}. The following code snippet shows how to construct and send the intent:
+</p>
+<pre>
+ // Creates a new Intent to insert or edit a contact
+ Intent intentInsertEdit = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ // Sets the MIME type
+ intentInsertEdit.setType(Contacts.CONTENT_ITEM_TYPE);
+ // Add code here to insert extended data, if desired
+ ...
+ // Sends the Intent with an request ID
+ startActivity(intentInsertEdit);
+</pre>
diff --git a/docs/html/training/contacts-provider/retrieve-details.jd b/docs/html/training/contacts-provider/retrieve-details.jd
new file mode 100644
index 0000000..0de3b67
--- /dev/null
+++ b/docs/html/training/contacts-provider/retrieve-details.jd
@@ -0,0 +1,378 @@
+page.title=Retrieving Details for a Contact
+
+trainingnavtop=true
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#RetrieveAll">Retrieve All Details for a Contact</a></li>
+ <li><a href="#RetrieveSpecific">Retrieve Specific Details for a Contact</a></li>
+</ol>
+
+<!-- other docs (NOT javadocs) -->
+<h2>You should also read</h2>
+<ul>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/contacts-provider.html">
+ Contacts Provider</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/components/loaders.html">Loaders</a>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button">
+ Download the sample
+ </a>
+ <p class="filename">ContactsList.zip</p>
+</div>
+
+</div>
+</div>
+<p>
+ This lesson shows how to retrieve detail data for a contact, such as email addresses, phone
+ numbers, and so forth. It's the details that users are looking for when they retrieve a contact.
+ You can give them all the details for a contact, or only display details of a particular type,
+ such as email addresses.
+</p>
+<p>
+ The steps in this lesson assume that you already have a
+ {@link android.provider.ContactsContract.Contacts} row for a contact the user has picked.
+ The <a href="retrieve-names.html">Retrieving Contact Names</a> lesson shows how to
+ retrieve a list of contacts.
+</p>
+<h2 id="RetrieveAll">Retrieve All Details for a Contact</h2>
+<p>
+ To retrieve all the details for a contact, search the
+ {@link android.provider.ContactsContract.Data} table for any rows that contain the contact's
+ {@link android.provider.ContactsContract.Data#LOOKUP_KEY}. This column is available in
+ the {@link android.provider.ContactsContract.Data} table, because the Contacts
+ Provider makes an implicit join between the {@link android.provider.ContactsContract.Contacts}
+ table and the {@link android.provider.ContactsContract.Data} table. The
+ {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY} column is described
+ in more detail in the <a href="retrieve-names.html">Retrieving Contact Names</a> lesson.
+</p>
+<p class="note">
+ <strong>Note:</strong> Retrieving all the details for a contact reduces the performance of a
+ device, because it needs to retrieve all of the columns in the
+ {@link android.provider.ContactsContract.Data} table. Consider the performance impact before
+ you use this technique.
+</p>
+<h3>Request permissions</h3>
+<p>
+ To read from the Contacts Provider, your app must have
+ {@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} permission.
+ To request this permission, add the following child element of
+ <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html">
+ &lt;manifest&gt;</a></code> to your manifest file:
+</p>
+<pre>
+ &lt;uses-permission android:name="android.permission.READ_CONTACTS" /&gt;
+</pre>
+<h3>Set up a projection</h3>
+<p>
+ Depending on the data type a row contains, it may use only a few columns or many. In addition,
+ the data is in different columns depending on the data type.
+ To ensure you get all the possible columns for all possible data types, you need to add all the
+ column names to your projection. Always retrieve
+ {@link android.provider.ContactsContract.Data#_ID Data._ID} if you're binding the result
+ {@link android.database.Cursor} to a {@link android.widget.ListView}; otherwise, the binding
+ won't work. Also retrieve {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE}
+ so you can identify the data type of each row you retrieve. For example:
+</p>
+<pre>
+ private static final String PROJECTION =
+ {
+ Data._ID,
+ Data.MIMETYPE,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15
+ };
+</pre>
+<p>
+ This projection retrieves all the columns for a row in the
+ {@link android.provider.ContactsContract.Data} table, using the column names defined in
+ the {@link android.provider.ContactsContract.Data} class.
+</p>
+<p>
+ Optionally, you can also use any other column constants defined in or inherited by the
+ {@link android.provider.ContactsContract.Data} class. Notice, however, that the columns
+ {@link android.provider.ContactsContract.DataColumns#SYNC1} through
+ {@link android.provider.ContactsContract.DataColumns#SYNC4} are meant to be used by sync
+ adapters, so their data is not useful.
+</p>
+<h3>Define the selection criteria</h3>
+<p>
+ Define a constant for your selection clause, an array to hold selection arguments, and a
+ variable to hold the selection value. Use
+ the {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} column to
+ find the contact. For example:
+</p>
+<pre>
+ // Defines the selection clause
+ private static final String SELECTION = Data.LOOKUP_KEY + " = ?";
+ // Defines the array to hold the search criteria
+ private String[] mSelectionArgs = { "" };
+ /*
+ * Defines a variable to contain the selection value. Once you
+ * have the Cursor from the Contacts table, and you've selected
+ * the desired row, move the row's LOOKUP_KEY value into this
+ * variable.
+ */
+ private String mLookupKey;
+</pre>
+<p>
+ Using "?" as a placeholder in your selection text expression ensures that the resulting search
+ is generated by binding rather than SQL compilation. This approach eliminates the
+ possibility of malicious SQL injection.
+</p>
+<h3>Define the sort order</h3>
+<p>
+ Define the sort order you want in the resulting {@link android.database.Cursor}. To
+ keep all rows for a particular data type together, sort by
+ {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE}. This query argument
+ groups all email rows together, all phone rows together, and so forth. For example:
+</p>
+<pre>
+ /*
+ * Defines a string that specifies a sort order of MIME type
+ */
+ private static final String SORT_ORDER = Data.MIMETYPE;
+</pre>
+<p class="note">
+ <strong>Note:</strong> Some data types don't use a subtype, so you can't sort on subtype.
+ Instead, you have to iterate through the returned {@link android.database.Cursor},
+ determine the data type of the current row, and store data for rows that use a subtype. When
+ you finish reading the cursor, you can then sort each data type by subtype and display the
+ results.
+</p>
+<h3>Initialize the Loader</h3>
+<p>
+ Always do retrievals from the Contacts Provider (and all other content providers) in a
+ background thread. Use the Loader framework defined by the
+ {@link android.support.v4.app.LoaderManager} class and the
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks} interface to do background
+ retrievals.
+</p>
+<p>
+ When you're ready to retrieve the rows, initialize the loader framework by
+ calling {@link android.support.v4.app.LoaderManager#initLoader initLoader()}. Pass an
+ integer identifier to the method; this identifier is passed to
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks} methods. The identifier helps you
+ use multiple loaders in an app by allowing you to differentiate between them.
+</p>
+<p>
+ The following snippet shows how to initialize the loader framework:
+</p>
+<pre>
+public class DetailsFragment extends Fragment implements
+ LoaderManager.LoaderCallbacks&lt;Cursor&gt; {
+ ...
+ // Defines a constant that identifies the loader
+ DETAILS_QUERY_ID = 0;
+ ...
+ /*
+ * Invoked when the parent Activity is instantiated
+ * and the Fragment's UI is ready. Put final initialization
+ * steps here.
+ */
+ &#64;Override
+ onActivityCreated(Bundle savedInstanceState) {
+ ...
+ // Initializes the loader framework
+ getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);
+</pre>
+<h3>Implement onCreateLoader()</h3>
+<p>
+ Implement the {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader
+ onCreateLoader()} method, which is called by the loader framework immediately after you call
+ {@link android.support.v4.app.LoaderManager#initLoader initLoader()}. Return a
+ {@link android.support.v4.content.CursorLoader} from this method. Since you're searching
+ the {@link android.provider.ContactsContract.Data} table, use the constant
+ {@link android.provider.ContactsContract.Data#CONTENT_URI Data.CONTENT_URI} as the content URI.
+ For example:
+</p>
+<pre>
+ &#64;Override
+ public Loader&lt;Cursor&gt; onCreateLoader(int loaderId, Bundle args) {
+ // Choose the proper action
+ switch (loaderId) {
+ case DETAILS_QUERY_ID:
+ // Assigns the selection parameter
+ mSelectionArgs[0] = mLookupKey;
+ // Starts the query
+ CursorLoader mLoader =
+ new CursorLoader(
+ getActivity(),
+ Data.CONTENT_URI,
+ PROJECTION,
+ SELECTION,
+ mSelectionArgs,
+ SORT_ORDER
+ );
+ ...
+ }
+</pre>
+<h3>Implement onLoadFinished() and onLoaderReset()</h3>
+<p>
+ Implement the
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}
+ method. The loader framework calls
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}
+ when the Contacts Provider returns the results of the query. For example:
+</p>
+<pre>
+ public void onLoadFinished(Loader&lt;Cursor&gt; loader, Cursor cursor) {
+ switch (loader.getId()) {
+ case DETAILS_QUERY_ID:
+ /*
+ * Process the resulting Cursor here.
+ */
+ }
+ break;
+ ...
+ }
+ }
+</pre>
+<p>
+<p>
+ The method {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset
+ onLoaderReset()} is invoked when the loader framework detects that the data backing the result
+ {@link android.database.Cursor} has changed. At this point, remove any existing references
+ to the {@link android.database.Cursor} by setting them to null. If you don't, the loader
+ framework won't destroy the old {@link android.database.Cursor}, and you'll get a memory
+ leak. For example:
+<pre>
+ &#64;Override
+ public void onLoaderReset(Loader&lt;Cursor&gt; loader) {
+ switch (loader.getId()) {
+ case DETAILS_QUERY_ID:
+ /*
+ * If you have current references to the Cursor,
+ * remove them here.
+ */
+ }
+ break;
+ }
+</pre>
+<h2 id="RetrieveSpecific">Retrieve Specific Details for a Contact</h2>
+<p>
+ Retrieving a specific data type for a contact, such as all the emails, follows the same pattern
+ as retrieving all details. These are the only changes you need to make to the code
+ listed in <a href="#RetrieveAll">Retrieve All Details for a Contact</a>:
+</p>
+<dl>
+ <dt>
+ Projection
+ </dt>
+ <dd>
+ Modify your projection to retrieve the columns that are specific to the
+ data type. Also modify the projection to use the column name constants defined in the
+ {@link android.provider.ContactsContract.CommonDataKinds} subclass corresponding to the
+ data type.
+ </dd>
+ <dt>
+ Selection
+ </dt>
+ <dd>
+ Modify the selection text to search for the
+ {@link android.provider.ContactsContract.Data#MIMETYPE MIMETYPE} value that's specific to
+ your data type.
+ </dd>
+ <dt>
+ Sort order
+ </dt>
+ <dd>
+ Since you're only selecting a single detail type, don't group the returned
+ {@link android.database.Cursor} by {@link android.provider.ContactsContract.Data#MIMETYPE
+ Data.MIMETYPE}.
+ </dd>
+</dl>
+<p>
+ These modifications are described in the following sections.
+</p>
+<h3>Define a projection</h3>
+<p>
+ Define the columns you want to retrieve, using the column name constants in the subclass
+ of {@link android.provider.ContactsContract.CommonDataKinds} for the data type.
+ If you plan to bind your {@link android.database.Cursor} to a {@link android.widget.ListView},
+ be sure to retrieve the <code>_ID</code> column. For example, to retrieve email data, define the
+ following projection:
+</p>
+<pre>
+ private static final String[] PROJECTION =
+ {
+ Email._ID,
+ Email.ADDRESS,
+ Email.TYPE,
+ Email.LABEL
+ };
+</pre>
+<p>
+ Notice that this projection uses the column names defined in the class
+ {@link android.provider.ContactsContract.CommonDataKinds.Email}, instead of the column names
+ defined in the class {@link android.provider.ContactsContract.Data}. Using the email-specific
+ column names makes the code more readable.
+</p>
+<p>
+ In the projection, you can also use any of the other columns defined in the
+ {@link android.provider.ContactsContract.CommonDataKinds} subclass.
+</p>
+<h3>Define selection criteria</h3>
+<p>
+ Define a search text expression that retrieves rows for a specific contact's
+ {@link android.provider.ContactsContract.Data#LOOKUP_KEY} and the
+ {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE} of the details you
+ want. Enclose the {@link android.provider.ContactsContract.Data#MIMETYPE MIMETYPE} value in
+ single quotes by concatenating a "<code>'</code>" (single-quote) character to the start and end
+ of the constant; otherwise, the provider interprets the constant as a variable name rather
+ than as a string value. You don't need to use a placeholder for this value, because you're
+ using a constant rather than a user-supplied value. For example:
+</p>
+<pre>
+ /*
+ * Defines the selection clause. Search for a lookup key
+ * and the Email MIME type
+ */
+ private static final String SELECTION =
+ Data.LOOKUP_KEY + " = ?" +
+ " AND " +
+ Data.MIMETYPE + " = " +
+ "'" + Email.CONTENT_ITEM_TYPE + "'";
+ // Defines the array to hold the search criteria
+ private String[] mSelectionArgs = { "" };
+</pre>
+<h3>Define a sort order</h3>
+<p>
+ Define a sort order for the returned {@link android.database.Cursor}. Since you're retrieving a
+ specific data type, omit the sort on {@link android.provider.ContactsContract.Data#MIMETYPE}.
+ Instead, if the type of detail data you're searching includes a subtype, sort on it.
+ For example, for email data you can sort on
+ {@link android.provider.ContactsContract.CommonDataKinds.Email#TYPE Email.TYPE}:
+</p>
+<pre>
+ private static final String SORT_ORDER = Email.TYPE + " ASC ";
+</pre>
diff --git a/docs/html/training/contacts-provider/retrieve-names.jd b/docs/html/training/contacts-provider/retrieve-names.jd
new file mode 100644
index 0000000..b034a6a
--- /dev/null
+++ b/docs/html/training/contacts-provider/retrieve-names.jd
@@ -0,0 +1,815 @@
+page.title=Retrieving a List of Contacts
+
+trainingnavtop=true
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#Permissions">Request Permission to Read the Provider</a>
+ <li><a href="#NameMatch">Match a Contact by Name and List the Results</a></li>
+ <li><a href="#TypeMatch">Match a Contact By a Specific Type of Data</a></li>
+ <li><a href="#GeneralMatch">Match a Contact By Any Type of Data</a></li>
+</ol>
+
+<!-- other docs (NOT javadocs) -->
+<h2>You should also read</h2>
+<ul>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/contacts-provider.html">
+ Contacts Provider</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/components/loaders.html">Loaders</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/search/search-dialog.html">Creating a Search Interface</a>
+ </li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+ <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button">
+ Download the sample
+ </a>
+ <p class="filename">ContactsList.zip</p>
+</div>
+
+</div>
+</div>
+<p>
+ This lesson shows you how to retrieve a list of contacts whose data matches all or part of a
+ search string, using the following techniques:
+</p>
+<dl>
+ <dt>Match contact names</dt>
+ <dd>
+ Retrieve a list of contacts by matching the search string to all or part of the contact
+ name data. The Contacts Provider allows multiple instances of the same name, so this
+ technique can return a list of matches.
+ </dd>
+ <dt>Match a specific type of data, such as a phone number</dt>
+ <dd>
+ Retrieve a list of contacts by matching the search string to a particular type of detail
+ data such as an email address. For example, this technique allows you to list all of the
+ contacts whose email address matches the search string.
+ </dd>
+ <dt>Match any type of data</dt>
+ <dd>
+ Retrieve a list of contacts by matching the search string to any type of detail data,
+ including name, phone number, street address, email address, and so forth. For example,
+ this technique allows you to accept any type of data for a search string and then list the
+ contacts for which the data matches the string.
+ </dd>
+</dl>
+<p class="note">
+ <strong>Note:</strong> All the examples in this lesson use a
+ {@link android.support.v4.content.CursorLoader} to retrieve data from the Contacts
+ Provider. A {@link android.support.v4.content.CursorLoader} runs its query on a
+ thread that's separate from the UI thread. This ensures that the query doesn't slow down UI
+ response times and cause a poor user experience. For more information, see the Android
+ training class <a href="{@docRoot}training/load-data-background/index.html">
+ Loading Data in the Background</a>.
+</p>
+<h2 id="Permissions">Request Permission to Read the Provider</h2>
+<p>
+ To do any type of search of the Contacts Provider, your app must have
+ {@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} permission.
+ To request this, add this
+<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">&lt;uses-permission&gt;</a></code>
+ element to your manifest file as a child element of
+<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html">&lt;manifest&gt;</a></code>:
+</p>
+<pre>
+ &lt;uses-permission android:name="android.permission.READ_CONTACTS" /&gt;
+</pre>
+<h2 id="NameMatch">Match a Contact by Name and List the Results</h2>
+<p>
+ This technique tries to match a search string to the name of a contact or contacts in the
+ Contact Provider's {@link android.provider.ContactsContract.Contacts} table. You usually want
+ to display the results in a {@link android.widget.ListView}, to allow the user to choose among
+ the matched contacts.
+</p>
+<h3 id="DefineListView">Define ListView and item layouts</h3>
+<p>
+ To display the search results in a {@link android.widget.ListView}, you need a main layout file
+ that defines the entire UI including the {@link android.widget.ListView}, and an item layout
+ file that defines one line of the {@link android.widget.ListView}. For example, you can define
+ the main layout file <code>res/layout/contacts_list_view.xml</code> that contains the
+ following XML:
+</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="&#64;android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/&gt;
+</pre>
+<p>
+ This XML uses the built-in Android {@link android.widget.ListView} widget
+ {@link android.R.id#list android:id/list}.
+</p>
+<p>
+ Define the item layout file <code>contacts_list_item.xml</code> with the following XML:
+</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="&#64;android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"/&gt;
+</pre>
+<p>
+ This XML uses the built-in Android {@link android.widget.TextView} widget
+ {@link android.R.id#text1 android:text1}.
+</p>
+<p class="note">
+ <strong>Note:</strong> This lesson doesn't describe the UI for getting a search string from the
+ user, because you may want to get the string indirectly. For example, you can give the user
+ an option to search for contacts whose name matches a string in an incoming text message.
+</p>
+<p>
+ The two layout files you've written define a user interface that shows a
+ {@link android.widget.ListView}. The next step is to write code that uses this UI to display a
+ list of contacts.
+</p>
+<h3 id="Fragment">Define a Fragment that displays the list of contacts</h3>
+<p>
+ To display the list of contacts, start by defining a {@link android.support.v4.app.Fragment}
+ that's loaded by an {@link android.app.Activity}. Using a
+ {@link android.support.v4.app.Fragment} is a more flexible technique, because you can use
+ one {@link android.support.v4.app.Fragment} to display the list and a second
+ {@link android.support.v4.app.Fragment} to display the details for a contact that the user
+ chooses from the list. Using this approach, you can combine one of the techniques presented in
+ this lesson with one from the lesson <a href="retrieve-details.html">
+ Retrieving Details for a Contact</a>.
+</p>
+<p>
+ To learn how to use one or more {@link android.support.v4.app.Fragment} objects from an
+ an {@link android.app.Activity}, read the training class
+ <a href="{@docRoot}training/basics/fragments/index.html">
+ Building a Dynamic UI with Fragments</a>.
+</p>
+<p>
+ To help you write queries against the Contacts Provider, the Android framework provides a
+ contracts class called {@link android.provider.ContactsContract}, which defines useful
+ constants and methods for accessing the provider. When you use this class, you don't have to
+ define your own constants for content URIs, table names, or columns. To use this class,
+ include the following statement:
+</p>
+<pre>
+import android.provider.ContactsContract;
+</pre>
+<p>
+ Since the code uses a {@link android.support.v4.content.CursorLoader} to retrieve data
+ from the provider, you must specify that it implements the loader interface
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks}. Also, to help detect which contact
+ the user selects from the list of search results, implement the adapter interface
+ {@link android.widget.AdapterView.OnItemClickListener}. For example:
+</p>
+<pre>
+...
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.widget.AdapterView;
+...
+public class ContactsFragment extends Fragment implements
+ LoaderManager.LoaderCallbacks&lt;Cursor&gt;,
+ AdapterView.OnItemClickListener {
+</pre>
+<h3 id="DefineVariables">Define global variables</h3>
+<p>
+ Define global variables that are used in other parts of the code:
+</p>
+<pre>
+ ...
+ /*
+ * Defines an array that contains column names to move from
+ * the Cursor to the ListView.
+ */
+ &#64;SuppressLint("InlinedApi")
+ private final static String[] FROM_COLUMNS = {
+ Build.VERSION.SDK_INT
+ &gt;= Build.VERSION_CODES.HONEYCOMB ?
+ Contacts.DISPLAY_NAME_PRIMARY :
+ Contacts.DISPLAY_NAME
+ };
+ /*
+ * Defines an array that contains resource ids for the layout views
+ * that get the Cursor column contents. The id is pre-defined in
+ * the Android framework, so it is prefaced with "android.R.id"
+ */
+ private final static int[] TO_IDS = {
+ android.R.id.text1
+ };
+ // Define global mutable variables
+ // Define a ListView object
+ ListView mContactsList;
+ // Define variables for the contact the user selects
+ // The contact's _ID value
+ long mContactId;
+ // The contact's LOOKUP_KEY
+ String mContactKey;
+ // A content URI for the selected contact
+ Uri mContactUri;
+ // An adapter that binds the result Cursor to the ListView
+ private SimpleCursorAdapter mCursorAdapter;
+ ...
+</pre>
+<p class="note">
+ <strong>Note:</strong> Since
+ {@link android.provider.ContactsContract.Contacts#DISPLAY_NAME_PRIMARY
+ Contacts.DISPLAY_NAME_PRIMARY} requires Android 3.0 (API version 11) or later, setting your
+ app's <code>minSdkVersion</code> to 10 or below generates an Android Lint warning in
+ Eclipse with ADK. To turn off this warning, add the annotation
+ <code>@SuppressLint("InlinedApi")</code> before the definition of <code>FROM_COLUMNS</code>.
+</p>
+<h3 id="InitializeFragment">Initialize the Fragment</h3>
+<p>
+
+ Initialize the {@link android.support.v4.app.Fragment}. Add the empty, public constructor
+ required by the Android system, and inflate the {@link android.support.v4.app.Fragment} object's
+ UI in the callback method {@link android.support.v4.app.Fragment#onCreateView onCreateView()}.
+ For example:
+</p>
+<pre>
+ // Empty public constructor, required by the system
+ public ContactsFragment() {}
+
+ // A UI Fragment must inflate its View
+ &#64;Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the fragment layout
+ return inflater.inflate(R.layout.contacts_list_layout, container, false);
+ }
+</pre>
+<h3 id="DefineAdapter">Set up the CursorAdapter for the ListView</h3>
+<p>
+ Set up the {@link android.support.v4.widget.SimpleCursorAdapter} that binds the results of the
+ search to the {@link android.widget.ListView}. To get the {@link android.widget.ListView} object
+ that displays the contacts, you need to call {@link android.app.Activity#findViewById
+ Activity.findViewById()} using the parent activity of the
+ {@link android.support.v4.app.Fragment}. Use the {@link android.content.Context} of the
+ parent activity when you call {@link android.widget.ListView#setAdapter setAdapter()}.
+ For example:
+</p>
+<pre>
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ ...
+ // Gets the ListView from the View list of the parent activity
+ mContactsList = (ListView) getActivity().findViewById(R.layout.contact_list_view);
+ // Gets a CursorAdapter
+ mCursorAdapter = new SimpleCursorAdapter(
+ getActivity(),
+ R.layout.contact_list_item,
+ null,
+ FROM_COLUMNS, TO_IDS,
+ 0);
+ // Sets the adapter for the ListView
+ mContactsList.setAdapter(mCursorAdapter);
+ }
+</pre>
+<h3 id="SetListener">Set the selected contact listener</h3>
+<p>
+ When you display the results of a search, you usually want to allow the user to select a
+ single contact for further processing. For example, when the user clicks a contact you can
+ display the contact's address on a map. To provide this feature, you first defined the current
+ {@link android.support.v4.app.Fragment} as the click listener by specifying that the class
+ implements {@link android.widget.AdapterView.OnItemClickListener}, as shown in the section
+ <a href="#Fragment">Define a Fragment that displays the list of contacts</a>.
+</p>
+<p>
+ To continue setting up the listener, bind it to the {@link android.widget.ListView} by
+ calling the method {@link android.widget.ListView#setOnItemClickListener
+ setOnItemClickListener()} in {@link android.support.v4.app.Fragment#onActivityCreated
+ onActivityCreated()}. For example:
+</p>
+<pre>
+ public void onActivityCreated(Bundle savedInstanceState) {
+ ...
+ // Set the item click listener to be the current fragment.
+ mContactsList.setOnItemClickListener(this);
+ ...
+ }
+</pre>
+<p>
+ Since you specified that the current {@link android.support.v4.app.Fragment} is the
+ {@link android.widget.AdapterView.OnItemClickListener OnItemClickListener} for the
+ {@link android.widget.ListView}, you now need to implement its required method
+ {@link android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()}, which
+ handles the click event. This is described in a succeeding section.
+</p>
+<h3 id="DefineProjection">Define a projection</h3>
+<p>
+ Define a constant that contains the columns you want to return from your query. Each item in
+ the {@link android.widget.ListView} displays the contact's display name,
+ which contains the main form of the contact's name. In Android 3.0 (API version 11) and later,
+ the name of this column is
+ {@link android.provider.ContactsContract.Contacts#DISPLAY_NAME_PRIMARY
+ Contacts.DISPLAY_NAME_PRIMARY}; in versions previous to that, its name is
+ {@link android.provider.ContactsContract.Contacts#DISPLAY_NAME Contacts.DISPLAY_NAME}.
+</p>
+<p>
+ The column {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} is used by the
+ {@link android.support.v4.widget.SimpleCursorAdapter} binding process.
+ {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and
+ {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY} are used together to
+ construct a content URI for the contact the user selects.
+</p>
+<pre>
+...
+&#64;SuppressLint("InlinedApi")
+private static final String[] PROJECTION =
+ {
+ Contacts._ID,
+ Contacts.LOOKUP_KEY,
+ Build.VERSION.SDK_INT
+ &gt;= Build.VERSION_CODES.HONEYCOMB ?
+ Contacts.DISPLAY_NAME_PRIMARY :
+ Contacts.DISPLAY_NAME
+
+ };
+</pre>
+<h3 id="DefineConstants">Define constants for the Cursor column indexes</h3>
+<p>
+ To get data from an individual column in a {@link android.database.Cursor}, you need
+ the column's index within the {@link android.database.Cursor}. You can define constants
+ for the indexes of the {@link android.database.Cursor} columns, because the indexes are
+ the same as the order of the column names in your projection. For example:
+</p>
+<pre>
+// The column index for the _ID column
+private static final int CONTACT_ID_INDEX = 0;
+// The column index for the LOOKUP_KEY column
+private static final int LOOKUP_KEY_INDEX = 1;
+</pre>
+<h3 id="SelectionCriteria">Specify the selection criteria</h3>
+<p>
+ To specify the data you want, create a combination of text expressions and variables
+ that tell the provider the data columns to search and the values to find.
+</p>
+<p>
+ For the text expression, define a constant that lists the search columns. Although this
+ expression can contain values as well, the preferred practice is to represent the values with
+ a "?" placeholder. During retrieval, the placeholder is replaced with values from an
+ array. Using "?" as a placeholder ensures that the search specification is generated by binding
+ rather than by SQL compilation. This practice eliminates the possibility of malicious SQL
+ injection. For example:
+</p>
+<pre>
+ // Defines the text expression
+ &#64;SuppressLint("InlinedApi")
+ private static final String SELECTION =
+ Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB ?
+ Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
+ Contacts.DISPLAY_NAME + " LIKE ?";
+ // Defines a variable for the search string
+ private String mSearchString;
+ // Defines the array to hold values that replace the ?
+ private String[] mSelectionArgs = { mSearchString };
+</pre>
+<h3 id="OnItemClick">Define the onItemClick() method</h3>
+<p>
+ In a previous section, you set the item click listener for the {@link android.widget.ListView}.
+ Now implement the action for the listener by defining the method
+ {@link android.widget.AdapterView.OnItemClickListener#onItemClick
+ AdapterView.OnItemClickListener.onItemClick()}:
+</p>
+<pre>
+ &#64;Override
+ public void onItemClick(
+ AdapterView&lt;?&gt; parent, View item, int position, long rowID) {
+ // Get the Cursor
+ Cursor cursor = parent.getAdapter().getCursor();
+ // Move to the selected contact
+ cursor.moveToPosition(position);
+ // Get the _ID value
+ mContactId = getLong(CONTACT_ID_INDEX);
+ // Get the selected LOOKUP KEY
+ mContactKey = getString(CONTACT_KEY_INDEX);
+ // Create the contact's content Uri
+ mContactUri = Contacts.getLookupUri(mContactId, mContactKey);
+ /*
+ * You can use mContactUri as the content URI for retrieving
+ * the details for a contact.
+ */
+ }
+</pre>
+<h3 id="InitializeLoader">Initialize the loader</h3>
+<p>
+ Since you're using a {@link android.support.v4.content.CursorLoader} to retrieve data,
+ you must initialize the background thread and other variables that control asynchronous
+ retrieval. Do the initialization in
+ {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, which
+ is invoked immediately before the {@link android.support.v4.app.Fragment} UI appears, as
+ shown in the following example:
+</p>
+<pre>
+public class ContactsFragment extends Fragment implements
+ LoaderManager.LoaderCallbacks&lt;Cursor&gt; {
+ ...
+ // Called just before the Fragment displays its UI
+ &#64;Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ // Always call the super method first
+ super.onActivityCreated(savedInstanceState);
+ ...
+ // Initializes the loader
+ getLoaderManager().initLoader(0, null, this);
+</pre>
+<h3 id="OnCreateLoader">Implement onCreateLoader()</h3>
+<p>
+ Implement the method
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()},
+ which is called by the loader framework immediately after you call
+ {@link android.support.v4.app.LoaderManager#initLoader initLoader()}.
+<p>
+ In {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()},
+ set up the search string pattern. To make a string into a pattern, insert "%"
+ (percent) characters to represent a sequence of zero or more characters, or "_" (underscore)
+ characters to represent a single character, or both. For example, the pattern "%Jefferson%"
+ would match both "Thomas Jefferson" and "Jefferson Davis".
+</p>
+<p>
+ Return a new {@link android.support.v4.content.CursorLoader} from the method. For the content
+ URI, use {@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}.
+ This URI refers to the entire table, as shown in the following example:
+</p>
+<pre>
+ ...
+ &#64;Override
+ public Loader&lt;Cursor&gt; onCreateLoader(int loaderId, Bundle args) {
+ /*
+ * Makes search string into pattern and
+ * stores it in the selection array
+ */
+ mSelectionArgs[0] = "%" + mSearchString + "%";
+ // Starts the query
+ return new CursorLoader(
+ getActivity(),
+ Contacts.CONTENT_URI,
+ PROJECTION,
+ SELECTION,
+ mSelectionArgs,
+ null
+ );
+ }
+</pre>
+<h3 id="FinishedReset">Implement onLoadFinished() and onLoaderReset()</h3>
+<p>
+ Implement the
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}
+ method. The loader framework calls
+ {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}
+ when the Contacts Provider returns the results of the query. In this method, put the
+ result {@link android.database.Cursor} in the
+ {@link android.support.v4.widget.SimpleCursorAdapter}. This automatically updates the
+ {@link android.widget.ListView} with the search results:
+</p>
+<pre>
+ public void onLoadFinished(Loader&lt;Cursor&gt; loader, Cursor cursor) {
+ // Put the result Cursor in the adapter for the ListView
+ mCursorAdapter.swapCursor(cursor);
+ }
+</pre>
+<p>
+ The method {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset
+ onLoaderReset()} is invoked when the loader framework detects that the
+ result {@link android.database.Cursor} contains stale data. Delete the
+ {@link android.support.v4.widget.SimpleCursorAdapter} reference to the existing
+ {@link android.database.Cursor}. If you don't, the loader framework will not
+ recycle the {@link android.database.Cursor}, which causes a memory leak. For example:
+</p>
+<pre>
+ &#64;Override
+ public void onLoaderReset(Loader&lt;Cursor&gt; loader) {
+ // Delete the reference to the existing Cursor
+ mCursorAdapter.swapCursor(null);
+
+ }
+</pre>
+
+<p>
+ You now have the key pieces of an app that matches a search string to contact names and returns
+ the result in a {@link android.widget.ListView}. The user can click a contact name to select it.
+ This triggers a listener, in which you can work further with the contact's data. For example,
+ you can retrieve the contact's details. To learn how to do this, continue with the next
+ lesson, <a href="#retrieve-details.html">Retrieving Details for a Contact</a>.
+</p>
+<p>
+ To learn more about search user interfaces, read the API guide
+ <a href="{@docRoot}guide/topics/search/search-dialog.html">Creating a Search Interface</a>.
+</p>
+<p>
+ The remaining sections in this lesson demonstrate other ways of finding contacts in the
+ Contacts Provider.
+</p>
+<h2 id="TypeMatch">Match a Contact By a Specific Type of Data</h2>
+<p>
+ This technique allows you to specify the type of data you want to match. Retrieving
+ by name is a specific example of this type of query, but you can also do it for any of the types
+ of detail data associated with a contact. For example, you can retrieve contacts that have a
+ specific postal code; in this case, the search string has to match data stored in a postal code
+ row.
+</p>
+<p>
+ To implement this type of retrieval, first implement the following code, as listed in
+ previous sections:
+</p>
+<ul>
+ <li>
+ Request Permission to Read the Provider.
+ </li>
+ <li>
+ Define ListView and item layouts.
+ </li>
+ <li>
+ Define a Fragment that displays the list of contacts.
+ </li>
+ <li>
+ Define global variables.
+ </li>
+ <li>
+ Initialize the Fragment.
+ </li>
+ <li>
+ Set up the CursorAdapter for the ListView.
+ </li>
+ <li>
+ Set the selected contact listener.
+ </li>
+ <li>
+ Define constants for the Cursor column indexes.
+ <p>
+ Although you're retrieving data from a different table, the order of the columns in
+ the projection is the same, so you can use the same indexes for the Cursor.
+ </p>
+ </li>
+ <li>
+ Define the onItemClick() method.
+ </li>
+ <li>
+ Initialize the loader.
+ </li>
+ <li>
+
+ Implement onLoadFinished() and onLoaderReset().
+ </li>
+</ul>
+<p>
+ The following steps show you the additional code you need to match a search string to
+ a particular type of detail data and display the results.
+</p>
+<h3>Choose the data type and table</h3>
+<p>
+ To search for a particular type of detail data, you have to know the custom MIME type value
+ for the data type. Each data type has a unique MIME type
+ value defined by a constant <code>CONTENT_ITEM_TYPE</code> in the subclass of
+ {@link android.provider.ContactsContract.CommonDataKinds} associated with the data type.
+ The subclasses have names that indicate their data type; for example, the subclass for email
+ data is {@link android.provider.ContactsContract.CommonDataKinds.Email}, and the custom MIME
+ type for email data is defined by the constant
+ {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE
+ Email.CONTENT_ITEM_TYPE}.
+</p>
+<p>
+ Use the {@link android.provider.ContactsContract.Data} table for your search. All of the
+ constants you need for your projection, selection clause, and sort order are defined in or
+ inherited by this table.
+</p>
+<h3 id="SpecificProjection">Define a projection</h3>
+<p>
+ To define a projection, choose one or more of the columns defined in
+ {@link android.provider.ContactsContract.Data} or the classes from which it inherits. The
+ Contacts Provider does an implicit join between {@link android.provider.ContactsContract.Data}
+ and other tables before it returns rows. For example:
+</p>
+<pre>
+ &#64;SuppressLint("InlinedApi")
+ private static final String[] PROJECTION =
+ {
+ /*
+ * The detail data row ID. To make a ListView work,
+ * this column is required.
+ */
+ Data._ID,
+ // The primary display name
+ Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB ?
+ Data.DISPLAY_NAME_PRIMARY :
+ Data.DISPLAY_NAME,
+ // The contact's _ID, to construct a content URI
+ Data.CONTACT_ID
+ // The contact's LOOKUP_KEY, to construct a content URI
+ Data.LOOKUP_KEY (a permanent link to the contact
+ };
+</pre>
+<h3 id="SpecificCriteria">Define search criteria</h3>
+<p>
+ To search for a string within a particular type of data, construct a selection clause from
+ the following:
+</p>
+<ul>
+ <li>
+ The name of the column that contains your search string. This name varies by data type,
+ so you need to find the subclass of
+ {@link android.provider.ContactsContract.CommonDataKinds} that corresponds to the data type
+ and then choose the column name from that subclass. For example, to search for
+ email addresses, use the column
+ {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS Email.ADDRESS}.
+ </li>
+ <li>
+ The search string itself, represented as the "?" character in the selection clause.
+ </li>
+ <li>
+ The name of the column that contains the custom MIME type value. This name is always
+ {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE}.
+ </li>
+ <li>
+ The custom MIME type value for the data type. As described previously, this is the constant
+ <code>CONTENT_ITEM_TYPE</code> in the
+ {@link android.provider.ContactsContract.CommonDataKinds} subclass. For example, the MIME
+ type value for email data is
+ {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE
+ Email.CONTENT_ITEM_TYPE}. Enclose the value in single quotes by concatenating a
+ "<code>'</code>" (single quote) character to the start and end of the constant; otherwise,
+ the provider interprets the value as a variable name rather than as a string value.
+ You don't need to use a placeholder for this value, because you're using a constant
+ rather than a user-supplied value.
+ </li>
+</ul>
+<p>
+ For example:
+</p>
+<pre>
+ /*
+ * Constructs search criteria from the search string
+ * and email MIME type
+ */
+ private static final String SELECTION =
+ /*
+ * Searches for an email address
+ * that matches the search string
+ */
+ Email.ADDRESS + " LIKE ? " + "AND " +
+ /*
+ * Searches for a MIME type that matches
+ * the value of the constant
+ * Email.CONTENT_ITEM_TYPE. Note the
+ * single quotes surrounding Email.CONTENT_ITEM_TYPE.
+ */
+ Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";
+</pre>
+<p>
+ Next, define variables to contain the selection argument:
+</p>
+<pre>
+ String mSearchString;
+ String[] mSelectionArgs = { "" };
+</pre>
+<h3 id="SpecificLoader">Implement onCreateLoader()</h3>
+<p>
+ Now that you've specified the data you want and how to find it, define a query in your
+ implementation of {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader
+ onCreateLoader()}. Return a new {@link android.support.v4.content.CursorLoader} from this
+ method, using your projection, selection text expression, and selection array as
+ arguments. For a content URI, use
+ {@link android.provider.ContactsContract.Data#CONTENT_URI Data.CONTENT_URI}. For example:
+</p>
+<pre>
+ &#64;Override
+ public Loader&lt;Cursor&gt; onCreateLoader(int loaderId, Bundle args) {
+ // OPTIONAL: Makes search string into pattern
+ mSearchString = "%" + mSearchString + "%";
+ // Puts the search string into the selection criteria
+ mSelectionArgs[0] = mSearchString;
+ // Starts the query
+ return new CursorLoader(
+ getActivity(),
+ Data.CONTENT_URI,
+ PROJECTION,
+ SELECTION,
+ mSelectionArgs,
+ null
+ );
+ }
+</pre>
+<p>
+ These code snippets are the basis of a simple reverse lookup based on a specific type of detail
+ data. This is the best technique to use if your app focuses on a particular type of data, such
+ as emails, and you want allow users to get the names associated with a piece of data.
+</p>
+<h2 id="GeneralMatch">Match a Contact By Any Type of Data</h2>
+<p>
+ Retrieving a contact based on any type of data returns contacts if any of their data matches a
+ the search string, including name, email address, postal address, phone number, and so forth.
+ This results in a broad set of search results. For example, if the search string
+ is "Doe", then searching for any data type returns the contact "John Doe"; it also returns
+ contacts who live on "Doe Street".
+</p>
+<p>
+ To implement this type of retrieval, first implement the following code, as listed in
+ previous sections:
+</p>
+<ul>
+ <li>
+ Request Permission to Read the Provider.
+ </li>
+ <li>
+ Define ListView and item layouts.
+ </li>
+ <li>
+ <li>
+ Define a Fragment that displays the list of contacts.
+ </li>
+ <li>
+ Define global variables.
+ </li>
+ <li>
+ Initialize the Fragment.
+ </li>
+ <li>
+ Set up the CursorAdapter for the ListView.
+ </li>
+ <li>
+ Set the selected contact listener.
+ </li>
+ <li>
+ Define a projection.
+ </li>
+ <li>
+ Define constants for the Cursor column indexes.
+ <p>
+ For this type of retrieval, you're using the same table you used in the section
+ <a href="#NameMatch">Match a Contact by Name and List the Results</a>. Use the
+ same column indexes as well.
+ </p>
+ </li>
+ <li>
+ Define the onItemClick() method.
+ </li>
+ <li>
+ Initialize the loader.
+ </li>
+ <li>
+
+ Implement onLoadFinished() and onLoaderReset().
+ </li>
+</ul>
+<p>
+ The following steps show you the additional code you need to match a search string to
+ any type of data and display the results.
+</p>
+<h3 id="NoSelection">Remove selection criteria</h3>
+<p>
+ Don't define the <code>SELECTION</code> constants or the <code>mSelectionArgs</code> variable.
+ These aren't used in this type of retrieval.
+</p>
+<h3 id="CreateLoaderAny">Implement onCreateLoader()</h3>
+<p>
+ Implement the {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader
+ onCreateLoader()} method, returning a new {@link android.support.v4.content.CursorLoader}.
+ You don't need to convert the search string into a pattern, because the Contacts Provider does
+ that automatically. Use
+ {@link android.provider.ContactsContract.Contacts#CONTENT_FILTER_URI
+ Contacts.CONTENT_FILTER_URI} as the base URI, and append your search string to it by calling
+ {@link android.net.Uri#withAppendedPath Uri.withAppendedPath()}. Using this URI
+ automatically triggers searching for any data type, as shown in the following example:
+</p>
+<pre>
+ &#64;Override
+ public Loader&lt;Cursor&gt; onCreateLoader(int loaderId, Bundle args) {
+ /*
+ * Appends the search string to the base URI. Always
+ * encode search strings to ensure they're in proper
+ * format.
+ */
+ Uri contentUri = Uri.withAppendedPath(
+ Contacts.CONTENT_FILTER_URI,
+ Uri.encode(mSearchString));
+ // Starts the query
+ return new CursorLoader(
+ getActivity(),
+ contentUri,
+ PROJECTION,
+ null,
+ null,
+ null
+ );
+ }
+</pre>
+<p>
+ These code snippets are the basis of an app that does a broad search of the Contacts Provider.
+ The technique is useful for apps that want to implement functionality similar to the
+ People app's contact list screen.
+</p>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 985fc44..7a3f2ca 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -484,7 +484,37 @@
</a>
</div>
<ul>
-
+ <li class="nav-section">
+ <div class="nav-section-header">
+ <a href="<?cs var:toroot ?>training/contacts-provider/index.html"
+ description=
+ "How to use Android's central address book, the Contacts Provider, to
+ display contacts and their details and modify contact information.">
+ Accessing Contacts Data</a>
+ </div>
+ <ul>
+ <li>
+ <a href="<?cs var:toroot ?>training/contacts-provider/retrieve-names.html">
+ Retrieving a List of Contacts
+ </a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/contacts-provider/retrieve-details.html">
+ Retrieving Details for a Contact
+ </a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/contacts-provider/modify-data.html">
+ Modifying Contacts Using Intents
+ </a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/contacts-provider/display-contact-badge.html">
+ Displaying the Quick Contact Badge
+ </a>
+ </li>
+ </ul>
+ </li>
<li class="nav-section">
<div class="nav-section-header">
<a href="<?cs var:toroot ?>training/id-auth/index.html"
@@ -879,7 +909,7 @@
</ul>
</li>
</ul>
- </li> <!-- end of User Input -->
+ </li> <!-- end of User Input -->
<li class="nav-section">
<div class="nav-section-header">
diff --git a/graphics/java/android/renderscript/Type.java b/graphics/java/android/renderscript/Type.java
index 9507030..d7c8255 100644
--- a/graphics/java/android/renderscript/Type.java
+++ b/graphics/java/android/renderscript/Type.java
@@ -110,6 +110,16 @@ public class Type extends BaseObj {
}
/**
+ * Get the YUV format
+ *
+ * @hide
+ * @return int
+ */
+ public int getYuv() {
+ return mDimYuv;
+ }
+
+ /**
* Return if the Type has a mipmap chain.
*
* @return boolean
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 18f3b1e..38cdb8a 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -165,6 +165,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 28;
private static final int MSG_PROMOTE_RCC = 29;
private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 30;
+ private static final int MSG_UNLOAD_SOUND_EFFECTS = 31;
// flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
@@ -1685,6 +1686,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private static final String ASSET_FILE_VERSION = "1.0";
private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
+ private static final int SOUND_EFECTS_LOAD_TIMEOUT_MS = 5000;
+
+ class LoadSoundEffectReply {
+ public int mStatus = 1;
+ };
+
private void loadTouchSoundAssetDefaults() {
SOUND_EFFECT_FILES.add("Effect_Tick.ogg");
for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {
@@ -1696,6 +1703,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private void loadTouchSoundAssets() {
XmlResourceParser parser = null;
+ // only load assets once.
+ if (!SOUND_EFFECT_FILES.isEmpty()) {
+ return;
+ }
+
loadTouchSoundAssetDefaults();
try {
@@ -1765,14 +1777,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
/** @see AudioManager#playSoundEffect(int) */
public void playSoundEffect(int effectType) {
- sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_NOOP,
- effectType, -1, null, 0);
+ playSoundEffectVolume(effectType, -1.0f);
}
/** @see AudioManager#playSoundEffect(int, float) */
public void playSoundEffectVolume(int effectType, float volume) {
- loadSoundEffects();
- sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_NOOP,
+ sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
effectType, (int) (volume * 1000), null, 0);
}
@@ -1781,116 +1791,20 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
* This method must be called at first when sound effects are enabled
*/
public boolean loadSoundEffects() {
- int status;
-
- loadTouchSoundAssets();
-
- synchronized (mSoundEffectsLock) {
- if (!mBootCompleted) {
- Log.w(TAG, "loadSoundEffects() called before boot complete");
- return false;
- }
-
- if (mSoundPool != null) {
- return true;
- }
- mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0);
-
- try {
- mSoundPoolCallBack = null;
- mSoundPoolListenerThread = new SoundPoolListenerThread();
- mSoundPoolListenerThread.start();
- // Wait for mSoundPoolCallBack to be set by the other thread
- mSoundEffectsLock.wait();
- } catch (InterruptedException e) {
- Log.w(TAG, "Interrupted while waiting sound pool listener thread.");
- }
-
- if (mSoundPoolCallBack == null) {
- Log.w(TAG, "loadSoundEffects() could not create SoundPool listener or thread");
- if (mSoundPoolLooper != null) {
- mSoundPoolLooper.quit();
- mSoundPoolLooper = null;
- }
- mSoundPoolListenerThread = null;
- mSoundPool.release();
- mSoundPool = null;
- return false;
- }
- /*
- * poolId table: The value -1 in this table indicates that corresponding
- * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
- * Once loaded, the value in poolId is the sample ID and the same
- * sample can be reused for another effect using the same file.
- */
- int[] poolId = new int[SOUND_EFFECT_FILES.size()];
- for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
- poolId[fileIdx] = -1;
- }
- /*
- * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
- * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
- * this indicates we have a valid sample loaded for this effect.
- */
-
- int lastSample = 0;
- for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
- // Do not load sample if this effect uses the MediaPlayer
- if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
- continue;
- }
- if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
- String filePath = Environment.getRootDirectory()
- + SOUND_EFFECTS_PATH
- + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effect][0]);
- int sampleId = mSoundPool.load(filePath, 0);
- if (sampleId <= 0) {
- Log.w(TAG, "Soundpool could not load file: "+filePath);
- } else {
- SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
- poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
- lastSample = sampleId;
- }
- } else {
- SOUND_EFFECT_FILES_MAP[effect][1] = poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
- }
- }
- // wait for all samples to be loaded
- if (lastSample != 0) {
- mSoundPoolCallBack.setLastSample(lastSample);
+ int attempts = 3;
+ LoadSoundEffectReply reply = new LoadSoundEffectReply();
+ synchronized (reply) {
+ sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
+ while ((reply.mStatus == 1) && (attempts-- > 0)) {
try {
- mSoundEffectsLock.wait();
- status = mSoundPoolCallBack.status();
- } catch (java.lang.InterruptedException e) {
- Log.w(TAG, "Interrupted while waiting sound pool callback.");
- status = -1;
- }
- } else {
- status = -1;
- }
-
- if (mSoundPoolLooper != null) {
- mSoundPoolLooper.quit();
- mSoundPoolLooper = null;
- }
- mSoundPoolListenerThread = null;
- if (status != 0) {
- Log.w(TAG,
- "loadSoundEffects(), Error "
- + ((lastSample != 0) ? mSoundPoolCallBack.status() : -1)
- + " while loading samples");
- for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
- if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) {
- SOUND_EFFECT_FILES_MAP[effect][1] = -1;
- }
+ reply.wait(SOUND_EFECTS_LOAD_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
}
-
- mSoundPool.release();
- mSoundPool = null;
}
}
- return (status == 0);
+ return (reply.mStatus == 0);
}
/**
@@ -1899,32 +1813,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
* sound effects are disabled.
*/
public void unloadSoundEffects() {
- synchronized (mSoundEffectsLock) {
- if (mSoundPool == null) {
- return;
- }
-
- mAudioHandler.removeMessages(MSG_LOAD_SOUND_EFFECTS);
- mAudioHandler.removeMessages(MSG_PLAY_SOUND_EFFECT);
-
- int[] poolId = new int[SOUND_EFFECT_FILES.size()];
- for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
- poolId[fileIdx] = 0;
- }
-
- for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
- if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) {
- continue;
- }
- if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) {
- mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]);
- SOUND_EFFECT_FILES_MAP[effect][1] = -1;
- poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1;
- }
- }
- mSoundPool.release();
- mSoundPool = null;
- }
+ sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0);
}
class SoundPoolListenerThread extends Thread {
@@ -1952,23 +1841,30 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private final class SoundPoolCallback implements
android.media.SoundPool.OnLoadCompleteListener {
- int mStatus;
- int mLastSample;
+ int mStatus = 1; // 1 means neither error nor last sample loaded yet
+ List<Integer> mSamples = new ArrayList<Integer>();
public int status() {
return mStatus;
}
- public void setLastSample(int sample) {
- mLastSample = sample;
+ public void setSamples(int[] samples) {
+ for (int i = 0; i < samples.length; i++) {
+ // do not wait ack for samples rejected upfront by SoundPool
+ if (samples[i] > 0) {
+ mSamples.add(samples[i]);
+ }
+ }
}
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
synchronized (mSoundEffectsLock) {
- if (status != 0) {
- mStatus = status;
+ int i = mSamples.indexOf(sampleId);
+ if (i >= 0) {
+ mSamples.remove(i);
}
- if (sampleId == mLastSample) {
+ if ((status != 0) || mSamples. isEmpty()) {
+ mStatus = status;
mSoundEffectsLock.notify();
}
}
@@ -3404,11 +3300,163 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
}
- private void playSoundEffect(int effectType, int volume) {
+ private boolean onLoadSoundEffects() {
+ int status;
+
+ synchronized (mSoundEffectsLock) {
+ if (!mBootCompleted) {
+ Log.w(TAG, "onLoadSoundEffects() called before boot complete");
+ return false;
+ }
+
+ if (mSoundPool != null) {
+ return true;
+ }
+
+ loadTouchSoundAssets();
+
+ mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0);
+ mSoundPoolCallBack = null;
+ mSoundPoolListenerThread = new SoundPoolListenerThread();
+ mSoundPoolListenerThread.start();
+ int attempts = 3;
+ while ((mSoundPoolCallBack == null) && (attempts-- > 0)) {
+ try {
+ // Wait for mSoundPoolCallBack to be set by the other thread
+ mSoundEffectsLock.wait(SOUND_EFECTS_LOAD_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting sound pool listener thread.");
+ }
+ }
+
+ if (mSoundPoolCallBack == null) {
+ Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error");
+ if (mSoundPoolLooper != null) {
+ mSoundPoolLooper.quit();
+ mSoundPoolLooper = null;
+ }
+ mSoundPoolListenerThread = null;
+ mSoundPool.release();
+ mSoundPool = null;
+ return false;
+ }
+ /*
+ * poolId table: The value -1 in this table indicates that corresponding
+ * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
+ * Once loaded, the value in poolId is the sample ID and the same
+ * sample can be reused for another effect using the same file.
+ */
+ int[] poolId = new int[SOUND_EFFECT_FILES.size()];
+ for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
+ poolId[fileIdx] = -1;
+ }
+ /*
+ * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
+ * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
+ * this indicates we have a valid sample loaded for this effect.
+ */
+
+ int numSamples = 0;
+ for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+ // Do not load sample if this effect uses the MediaPlayer
+ if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
+ continue;
+ }
+ if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
+ String filePath = Environment.getRootDirectory()
+ + SOUND_EFFECTS_PATH
+ + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effect][0]);
+ int sampleId = mSoundPool.load(filePath, 0);
+ if (sampleId <= 0) {
+ Log.w(TAG, "Soundpool could not load file: "+filePath);
+ } else {
+ SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
+ poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
+ numSamples++;
+ }
+ } else {
+ SOUND_EFFECT_FILES_MAP[effect][1] =
+ poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
+ }
+ }
+ // wait for all samples to be loaded
+ if (numSamples > 0) {
+ mSoundPoolCallBack.setSamples(poolId);
+
+ attempts = 3;
+ status = 1;
+ while ((status == 1) && (attempts-- > 0)) {
+ try {
+ mSoundEffectsLock.wait(SOUND_EFECTS_LOAD_TIMEOUT_MS);
+ status = mSoundPoolCallBack.status();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting sound pool callback.");
+ }
+ }
+ } else {
+ status = -1;
+ }
+
+ if (mSoundPoolLooper != null) {
+ mSoundPoolLooper.quit();
+ mSoundPoolLooper = null;
+ }
+ mSoundPoolListenerThread = null;
+ if (status != 0) {
+ Log.w(TAG,
+ "onLoadSoundEffects(), Error "+status+ " while loading samples");
+ for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+ if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) {
+ SOUND_EFFECT_FILES_MAP[effect][1] = -1;
+ }
+ }
+
+ mSoundPool.release();
+ mSoundPool = null;
+ }
+ }
+ return (status == 0);
+ }
+
+ /**
+ * Unloads samples from the sound pool.
+ * This method can be called to free some memory when
+ * sound effects are disabled.
+ */
+ private void onUnloadSoundEffects() {
synchronized (mSoundEffectsLock) {
if (mSoundPool == null) {
return;
}
+
+ int[] poolId = new int[SOUND_EFFECT_FILES.size()];
+ for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
+ poolId[fileIdx] = 0;
+ }
+
+ for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+ if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) {
+ continue;
+ }
+ if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) {
+ mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]);
+ SOUND_EFFECT_FILES_MAP[effect][1] = -1;
+ poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1;
+ }
+ }
+ mSoundPool.release();
+ mSoundPool = null;
+ }
+ }
+
+ private void onPlaySoundEffect(int effectType, int volume) {
+ synchronized (mSoundEffectsLock) {
+
+ onLoadSoundEffects();
+
+ if (mSoundPool == null) {
+ return;
+ }
float volFloat;
// use default if volume is not specified by caller
if (volume < 0) {
@@ -3418,7 +3466,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
- mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f);
+ mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
+ volFloat, volFloat, 0, 0, 1.0f);
} else {
MediaPlayer mediaPlayer = new MediaPlayer();
try {
@@ -3598,12 +3647,25 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
AudioSystem.setParameters("restarting=false");
break;
+ case MSG_UNLOAD_SOUND_EFFECTS:
+ onUnloadSoundEffects();
+ break;
+
case MSG_LOAD_SOUND_EFFECTS:
- loadSoundEffects();
+ //FIXME: onLoadSoundEffects() should be executed in a separate thread as it
+ // can take several dozens of milliseconds to complete
+ boolean loaded = onLoadSoundEffects();
+ if (msg.obj != null) {
+ LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj;
+ synchronized (reply) {
+ reply.mStatus = loaded ? 0 : -1;
+ reply.notify();
+ }
+ }
break;
case MSG_PLAY_SOUND_EFFECT:
- playSoundEffect(msg.arg1, msg.arg2);
+ onPlaySoundEffect(msg.arg1, msg.arg2);
break;
case MSG_BTA2DP_DOCK_TIMEOUT:
@@ -4151,7 +4213,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
} else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
mBootCompleted = true;
- sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_NOOP,
+ sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE,
0, 0, null, 0);
mKeyguardManager =
diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp
index 517a293..d0f56ea 100644
--- a/media/jni/android_media_MediaCrypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -74,7 +74,7 @@ sp<ICrypto> JCrypto::MakeCrypto() {
sp<ICrypto> crypto = service->makeCrypto();
- if (crypto == NULL || crypto->initCheck() != OK) {
+ if (crypto == NULL || (crypto->initCheck() != OK && crypto->initCheck() != NO_INIT)) {
return NULL;
}
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index d4f932e..430721e 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -165,6 +165,8 @@ struct InputTarget {
* Input dispatcher configuration.
*
* Specifies various options that modify the behavior of the input dispatcher.
+ * The values provided here are merely defaults. The actual values will come from ViewConfiguration
+ * and are passed into the dispatcher during initialization.
*/
struct InputDispatcherConfiguration {
// The key repeat initial timeout.
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a45c0ff..2a3c87e 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -63,6 +63,7 @@ import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SELinux;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -746,6 +747,9 @@ class BackupManagerService extends IBackupManager.Stub {
// correct directory.
mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup");
mBaseStateDir.mkdirs();
+ if (!SELinux.restorecon(mBaseStateDir)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
+ }
mDataDir = Environment.getDownloadCacheDirectory();
mPasswordHashFile = new File(mBaseStateDir, "pwhash");
@@ -2136,6 +2140,10 @@ class BackupManagerService extends IBackupManager.Stub {
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
+ if (!SELinux.restorecon(mBackupDataName)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
+ }
+
mNewState = ParcelFileDescriptor.open(mNewStateName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
@@ -4697,6 +4705,10 @@ class BackupManagerService extends IBackupManager.Stub {
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
+ if (!SELinux.restorecon(mBackupDataName)) {
+ Slog.e(TAG, "SElinux restorecon failed for " + mBackupDataName);
+ }
+
if (mTransport.getRestoreData(mBackupData) != BackupConstants.TRANSPORT_OK) {
// Transport-level failure, so we wind everything up and
// terminate the restore operation.
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 18b4ec1..d3e7c24 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -41,10 +41,14 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -62,6 +66,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.AtomicFile;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
@@ -88,10 +93,11 @@ import java.util.Set;
* Implementation of the device policy APIs.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
- private static final String DEVICE_POLICIES_XML = "device_policies.xml";
private static final String TAG = "DevicePolicyManagerService";
+ private static final String DEVICE_POLICIES_XML = "device_policies.xml";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = 86400 * 1000;
@@ -109,6 +115,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
IPowerManager mIPowerManager;
IWindowManager mIWindowManager;
+ private DeviceOwner mDeviceOwner;
+
public static class DevicePolicyData {
int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
int mActivePasswordLength = 0;
@@ -507,6 +515,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
}
@@ -545,6 +554,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ void loadDeviceOwner() {
+ synchronized (this) {
+ if (DeviceOwner.isRegistered()) {
+ mDeviceOwner = new DeviceOwner();
+ }
+ }
+ }
+
/**
* Set an alarm for an upcoming event - expiration warning, expiration, or post-expiration
* reminders. Clears alarm if no expirations are configured.
@@ -709,7 +726,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Intent resolveIntent = new Intent();
resolveIntent.setComponent(adminName);
List<ResolveInfo> infos = mContext.getPackageManager().queryBroadcastReceivers(
- resolveIntent, PackageManager.GET_META_DATA, userHandle);
+ resolveIntent,
+ PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ userHandle);
if (infos == null || infos.size() <= 0) {
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
@@ -994,6 +1013,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void systemReady() {
synchronized (this) {
loadSettingsLocked(getUserData(UserHandle.USER_OWNER), UserHandle.USER_OWNER);
+ loadDeviceOwner();
}
}
@@ -1052,6 +1072,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (replaceIndex == -1) {
policy.mAdminList.add(newAdmin);
+ enableIfNecessary(info.getPackageName(), userHandle);
} else {
policy.mAdminList.set(replaceIndex, newAdmin);
}
@@ -1119,6 +1140,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
if (admin.getUid() != Binder.getCallingUid()) {
+ // If trying to remove device owner, refuse when the caller is not the owner.
+ if (mDeviceOwner != null
+ && adminReceiver.getPackageName().equals(mDeviceOwner.getPackageName())) {
+ return;
+ }
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
}
@@ -2351,6 +2377,49 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ @Override
+ public boolean setDeviceOwner(String packageName) {
+ if (packageName == null
+ || !DeviceOwner.isInstalled(packageName, mContext.getPackageManager())) {
+ throw new IllegalArgumentException("Invalid package name " + packageName
+ + " for device owner");
+ }
+ synchronized (this) {
+ if (mDeviceOwner == null && !isDeviceProvisioned()) {
+ mDeviceOwner = new DeviceOwner(packageName);
+ mDeviceOwner.writeOwnerFile();
+ return true;
+ } else {
+ throw new IllegalStateException("Trying to set device owner to " + packageName
+ + ", owner=" + mDeviceOwner.getPackageName()
+ + ", device_provisioned=" + isDeviceProvisioned());
+ }
+ }
+ }
+
+ @Override
+ public boolean isDeviceOwner(String packageName) {
+ synchronized (this) {
+ return mDeviceOwner != null
+ && mDeviceOwner.getPackageName().equals(packageName);
+ }
+ }
+
+ @Override
+ public String getDeviceOwner() {
+ synchronized (this) {
+ if (mDeviceOwner != null) {
+ return mDeviceOwner.getPackageName();
+ }
+ }
+ return null;
+ }
+
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) > 0;
+ }
+
private void enforceCrossUserPermission(int userHandle) {
if (userHandle < 0) {
throw new IllegalArgumentException("Invalid userId " + userHandle);
@@ -2364,6 +2433,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private void enableIfNecessary(String packageName, int userId) {
+ try {
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ ApplicationInfo ai = ipm.getApplicationInfo(packageName,
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ userId);
+ if (ai.enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ ipm.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP, userId);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -2399,4 +2484,92 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
+
+ static class DeviceOwner {
+ private static final String DEVICE_OWNER_XML = "device_owner.xml";
+ private static final String TAG_DEVICE_OWNER = "device-owner";
+ private static final String ATTR_PACKAGE = "package";
+ private String mPackageName;
+
+ DeviceOwner() {
+ readOwnerFile();
+ }
+
+ DeviceOwner(String packageName) {
+ this.mPackageName = packageName;
+ }
+
+ static boolean isRegistered() {
+ return new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML).exists();
+ }
+
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ static boolean isInstalled(String packageName, PackageManager pm) {
+ try {
+ PackageInfo pi;
+ if ((pi = pm.getPackageInfo(packageName, 0)) != null) {
+ if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ }
+ } catch (NameNotFoundException nnfe) {
+ Slog.w(TAG, "Device Owner package " + packageName + " not installed.");
+ }
+ return false;
+ }
+
+ void readOwnerFile() {
+ AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML));
+ try {
+ FileInputStream input = file.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(input, null);
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+ String tag = parser.getName();
+ if (!TAG_DEVICE_OWNER.equals(tag)) {
+ throw new XmlPullParserException(
+ "Device Owner file does not start with device-owner tag: found " + tag);
+ }
+ mPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+ input.close();
+ } catch (XmlPullParserException xppe) {
+ Slog.e(TAG, "Error parsing device-owner file\n" + xppe);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "IO Exception when reading device-owner file\n" + ioe);
+ }
+ }
+
+ void writeOwnerFile() {
+ synchronized (this) {
+ writeOwnerFileLocked();
+ }
+ }
+
+ private void writeOwnerFileLocked() {
+ AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML));
+ try {
+ FileOutputStream output = file.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(output, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, TAG_DEVICE_OWNER);
+ out.attribute(null, ATTR_PACKAGE, mPackageName);
+ out.endTag(null, TAG_DEVICE_OWNER);
+ out.endDocument();
+ out.flush();
+ file.finishWrite(output);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "IO Exception when writing device-owner file\n" + ioe);
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index c6aca2f..e8d7882 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -1603,7 +1603,7 @@ class MountService extends IMountService.Stub
boolean mounted = false;
try {
mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
- } catch (IllegalStateException e) {
+ } catch (IllegalArgumentException e) {
}
if (!mounted) {
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index 09daf56..c4b98ad 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -1425,9 +1425,9 @@ public class AccountManagerService
if (accountType == null) throw new IllegalArgumentException("accountType is null");
checkManageAccountsPermission();
- // Is user allowed to modify accounts?
- if (!getUserManager().getUserRestrictions(Binder.getCallingUserHandle())
- .getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) {
+ // Is user disallowed from modifying accounts?
+ if (getUserManager().getUserRestrictions(Binder.getCallingUserHandle())
+ .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
try {
response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
"User is not allowed to add an account!");
@@ -2572,7 +2572,7 @@ public class AccountManagerService
if (callingUid != android.os.Process.myUid()) {
Bundle restrictions = getUserManager().getUserRestrictions(
new UserHandle(UserHandle.getUserId(callingUid)));
- if (!restrictions.getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) {
+ if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
return false;
}
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 6f092bf..04ffbd9 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -2194,7 +2194,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// the PID of the new process, or else throw a RuntimeException.
Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
app.processName, uid, uid, gids, debugFlags, mountExternal,
- app.info.targetSdkVersion, null, null);
+ app.info.targetSdkVersion, app.info.seinfo, null);
BatteryStatsImpl bs = app.batteryStats.getBatteryStats();
synchronized (bs) {
diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java
index 02a2c1b..d9c85bf 100644
--- a/services/java/com/android/server/pm/Installer.java
+++ b/services/java/com/android/server/pm/Installer.java
@@ -188,7 +188,7 @@ public final class Installer {
}
}
- public int install(String name, int uid, int gid) {
+ public int install(String name, int uid, int gid, String seinfo) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
builder.append(name);
@@ -196,6 +196,8 @@ public final class Installer {
builder.append(uid);
builder.append(' ');
builder.append(gid);
+ builder.append(' ');
+ builder.append(seinfo != null ? seinfo : "!");
return execute(builder.toString());
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 09d1426..afdd294 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -362,6 +362,9 @@ public class PackageManagerService extends IPackageManager.Stub {
final HashMap<String, FeatureInfo> mAvailableFeatures =
new HashMap<String, FeatureInfo>();
+ // If mac_permissions.xml was found for seinfo labeling.
+ boolean mFoundPolicyFile;
+
// All available activities, for your resolving pleasure.
final ActivityIntentResolver mActivities =
new ActivityIntentResolver();
@@ -1029,8 +1032,11 @@ public class PackageManagerService extends IPackageManager.Stub {
readPermissions();
+ mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
+
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
mSdkVersion, mOnlyCore);
+
long startTime = SystemClock.uptimeMillis();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
@@ -2236,6 +2242,34 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private static void checkGrantRevokePermissions(PackageParser.Package pkg, BasePermission bp) {
+ int index = pkg.requestedPermissions.indexOf(bp.name);
+ if (index == -1) {
+ throw new SecurityException("Package " + pkg.packageName
+ + " has not requested permission " + bp.name);
+ }
+ boolean isNormal =
+ ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_NORMAL);
+ boolean isDangerous =
+ ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_DANGEROUS);
+ boolean isDevelopment =
+ ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
+
+ if (!isNormal && !isDangerous && !isDevelopment) {
+ throw new SecurityException("Permission " + bp.name
+ + " is not a changeable permission type");
+ }
+
+ if (isNormal || isDangerous) {
+ if (pkg.requestedPermissionsRequired.get(index)) {
+ throw new SecurityException("Can't change " + bp.name
+ + ". It is required by the application");
+ }
+ }
+ }
+
public void grantPermission(String packageName, String permissionName) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
@@ -2246,21 +2280,16 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final BasePermission bp = mSettings.mPermissions.get(permissionName);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + packageName);
- }
- if (!pkg.requestedPermissions.contains(permissionName)) {
- throw new SecurityException("Package " + packageName
- + " has not requested permission " + permissionName);
- }
- if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) == 0) {
- throw new SecurityException("Permission " + permissionName
- + " is not a development permission");
+ throw new IllegalArgumentException("Unknown permission: " + permissionName);
}
+
+ checkGrantRevokePermissions(pkg, bp);
+
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+ final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
if (gp.grantedPermissions.add(permissionName)) {
if (ps.haveGids) {
gp.gids = appendInts(gp.gids, bp.gids);
@@ -2282,21 +2311,16 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final BasePermission bp = mSettings.mPermissions.get(permissionName);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + packageName);
- }
- if (!pkg.requestedPermissions.contains(permissionName)) {
- throw new SecurityException("Package " + packageName
- + " has not requested permission " + permissionName);
- }
- if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) == 0) {
- throw new SecurityException("Permission " + permissionName
- + " is not a development permission");
+ throw new IllegalArgumentException("Unknown permission: " + permissionName);
}
+
+ checkGrantRevokePermissions(pkg, bp);
+
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+ final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
if (gp.grantedPermissions.remove(permissionName)) {
gp.grantedPermissions.remove(permissionName);
if (ps.haveGids) {
@@ -3676,9 +3700,9 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- private int createDataDirsLI(String packageName, int uid) {
+ private int createDataDirsLI(String packageName, int uid, String seinfo) {
int[] users = sUserManager.getUserIds();
- int res = mInstaller.install(packageName, uid, uid);
+ int res = mInstaller.install(packageName, uid, uid, seinfo);
if (res < 0) {
return res;
}
@@ -4020,6 +4044,10 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
}
+ if (mFoundPolicyFile) {
+ SELinuxMMAC.assignSeinfoValue(pkg);
+ }
+
pkg.applicationInfo.uid = pkgSetting.appId;
pkg.mExtras = pkgSetting;
@@ -4158,7 +4186,8 @@ public class PackageManagerService extends IPackageManager.Stub {
recovered = true;
// And now re-install the app.
- ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid);
+ ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
+ pkg.applicationInfo.seinfo);
if (ret == -1) {
// Ack should not happen!
msg = prefix + pkg.packageName
@@ -4204,7 +4233,8 @@ public class PackageManagerService extends IPackageManager.Stub {
Log.v(TAG, "Want this data dir: " + dataPath);
}
//invoke installer to do the actual installation
- int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid);
+ int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
+ pkg.applicationInfo.seinfo);
if (ret < 0) {
// Error from installer
mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
@@ -5915,7 +5945,7 @@ public class PackageManagerService extends IPackageManager.Stub {
null);
final int uid = Binder.getCallingUid();
- if (!isUserAllowed(UserHandle.getUserId(uid), UserManager.ALLOW_INSTALL_APPS)) {
+ if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
try {
observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
} catch (RemoteException re) {
@@ -5963,7 +5993,7 @@ public class PackageManagerService extends IPackageManager.Stub {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"installExistingPackage for user " + userId);
}
- if (!isUserAllowed(userId, UserManager.ALLOW_INSTALL_APPS)) {
+ if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
}
@@ -5997,13 +6027,13 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isUserAllowed(int userId, String restrictionKey) {
+ private boolean isUserRestricted(int userId, String restrictionKey) {
Bundle restrictions = sUserManager.getUserRestrictions(userId);
- if (!restrictions.getBoolean(UserManager.ALLOW_INSTALL_APPS)) {
- Log.w(TAG, "User does not have permission to: " + restrictionKey);
- return false;
+ if (restrictions.getBoolean(restrictionKey, false)) {
+ Log.w(TAG, "User is restricted: " + restrictionKey);
+ return true;
}
- return true;
+ return false;
}
@Override
@@ -8400,7 +8430,7 @@ public class PackageManagerService extends IPackageManager.Stub {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"deletePackage for user " + userId);
}
- if (!isUserAllowed(userId, UserManager.ALLOW_UNINSTALL_APPS)) {
+ if (isUserRestricted(userId, UserManager.DISALLOW_UNINSTALL_APPS)) {
try {
observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
} catch (RemoteException re) {
@@ -8446,7 +8476,8 @@ public class PackageManagerService extends IPackageManager.Stub {
IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
try {
- if (dpm != null && dpm.packageHasActiveAdmins(packageName, userId)) {
+ if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId)
+ || dpm.isDeviceOwner(packageName))) {
Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
}
diff --git a/services/java/com/android/server/pm/SELinuxMMAC.java b/services/java/com/android/server/pm/SELinuxMMAC.java
new file mode 100644
index 0000000..15d2a5a
--- /dev/null
+++ b/services/java/com/android/server/pm/SELinuxMMAC.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageParser;
+import android.content.pm.Signature;
+import android.os.Environment;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+import java.util.HashMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Centralized access to SELinux MMAC (middleware MAC) implementation.
+ * {@hide}
+ */
+public final class SELinuxMMAC {
+
+ private static final String TAG = "SELinuxMMAC";
+
+ private static final boolean DEBUG_POLICY = false;
+ private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
+
+ // Signature seinfo values read from policy.
+ private static final HashMap<Signature, String> sSigSeinfo =
+ new HashMap<Signature, String>();
+
+ // Package name seinfo values read from policy.
+ private static final HashMap<String, String> sPackageSeinfo =
+ new HashMap<String, String>();
+
+ // Locations of potential install policy files.
+ private static final File[] INSTALL_POLICY_FILE = {
+ new File(Environment.getDataDirectory(), "system/mac_permissions.xml"),
+ new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"),
+ null};
+
+ private static void flushInstallPolicy() {
+ sSigSeinfo.clear();
+ sPackageSeinfo.clear();
+ }
+
+ /**
+ * Parses an MMAC install policy from a predefined list of locations.
+ * @param none
+ * @return boolean indicating whether an install policy was correctly parsed.
+ */
+ public static boolean readInstallPolicy() {
+
+ return readInstallPolicy(INSTALL_POLICY_FILE);
+ }
+
+ /**
+ * Parses an MMAC install policy given as an argument.
+ * @param File object representing the path of the policy.
+ * @return boolean indicating whether the install policy was correctly parsed.
+ */
+ public static boolean readInstallPolicy(File policyFile) {
+
+ return readInstallPolicy(new File[]{policyFile,null});
+ }
+
+ private static boolean readInstallPolicy(File[] policyFiles) {
+
+ FileReader policyFile = null;
+ int i = 0;
+ while (policyFile == null && policyFiles != null && policyFiles[i] != null) {
+ try {
+ policyFile = new FileReader(policyFiles[i]);
+ break;
+ } catch (FileNotFoundException e) {
+ Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath());
+ }
+ i++;
+ }
+
+ if (policyFile == null) {
+ Slog.d(TAG, "No policy file found. All seinfo values will be null.");
+ return false;
+ }
+
+ Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath());
+
+ flushInstallPolicy();
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(policyFile);
+
+ XmlUtils.beginDocument(parser, "policy");
+ while (true) {
+ XmlUtils.nextElement(parser);
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ String tagName = parser.getName();
+ if ("signer".equals(tagName)) {
+ String cert = parser.getAttributeValue(null, "signature");
+ if (cert == null) {
+ Slog.w(TAG, "<signer> without signature at "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ Signature signature;
+ try {
+ signature = new Signature(cert);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "<signer> with bad signature at "
+ + parser.getPositionDescription(), e);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ String seinfo = readSeinfoTag(parser);
+ if (seinfo != null) {
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, "<signer> tag: (" + cert + ") assigned seinfo="
+ + seinfo);
+
+ sSigSeinfo.put(signature, seinfo);
+ }
+ } else if ("default".equals(tagName)) {
+ String seinfo = readSeinfoTag(parser);
+ if (seinfo != null) {
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, "<default> tag assigned seinfo=" + seinfo);
+
+ // The 'null' signature is the default seinfo value
+ sSigSeinfo.put(null, seinfo);
+ }
+ } else if ("package".equals(tagName)) {
+ String pkgName = parser.getAttributeValue(null, "name");
+ if (pkgName == null) {
+ Slog.w(TAG, "<package> without name at "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ String seinfo = readSeinfoTag(parser);
+ if (seinfo != null) {
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, "<package> tag: (" + pkgName +
+ ") assigned seinfo=" + seinfo);
+
+ sPackageSeinfo.put(pkgName, seinfo);
+ }
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Got execption parsing ", e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Got execption parsing ", e);
+ }
+ try {
+ policyFile.close();
+ } catch (IOException e) {
+ //omit
+ }
+ return true;
+ }
+
+ private static String readSeinfoTag(XmlPullParser parser) throws
+ IOException, XmlPullParserException {
+
+ int type;
+ int outerDepth = parser.getDepth();
+ String seinfo = null;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if ("seinfo".equals(tagName)) {
+ String seinfoValue = parser.getAttributeValue(null, "value");
+ if (seinfoValue != null) {
+ seinfo = seinfoValue;
+ } else {
+ Slog.w(TAG, "<seinfo> without value at "
+ + parser.getPositionDescription());
+ }
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ return seinfo;
+ }
+
+ /**
+ * Labels a package based on an seinfo tag from install policy.
+ * The label is attached to the ApplicationInfo instance of the package.
+ * @param PackageParser.Package object representing the package
+ * to labeled.
+ * @return String holding the value of the seinfo label that was assigned.
+ * Value may be null which indicates no seinfo label was assigned.
+ */
+ public static void assignSeinfoValue(PackageParser.Package pkg) {
+
+ /*
+ * Non system installed apps should be treated the same. This
+ * means that any post-loaded apk will be assigned the default
+ * tag, if one exists in the policy, else null, without respect
+ * to the signing key.
+ */
+ if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) ||
+ ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+
+ // We just want one of the signatures to match.
+ for (Signature s : pkg.mSignatures) {
+ if (s == null)
+ continue;
+
+ if (sSigSeinfo.containsKey(s)) {
+ String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(s);
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, "package (" + pkg.packageName +
+ ") labeled with seinfo=" + seinfo);
+
+ return;
+ }
+ }
+
+ // Check for seinfo labeled by package.
+ if (sPackageSeinfo.containsKey(pkg.packageName)) {
+ String seinfo = pkg.applicationInfo.seinfo = sPackageSeinfo.get(pkg.packageName);
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, "package (" + pkg.packageName +
+ ") labeled with seinfo=" + seinfo);
+ return;
+ }
+ }
+
+ // If we have a default seinfo value then great, otherwise
+ // we set a null object and that is what we started with.
+ String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(null);
+ if (DEBUG_POLICY_INSTALL)
+ Slog.i(TAG, "package (" + pkg.packageName +
+ ") labeled with seinfo=" + (seinfo == null ? "null" : seinfo));
+ }
+}
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 636b0e5..fecc2df 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -226,6 +226,13 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ @Override
+ public boolean isRestricted() {
+ synchronized (mPackagesLock) {
+ return getUserInfoLocked(UserHandle.getCallingUserId()).isRestricted();
+ }
+ }
+
/*
* Should be locked on mUsers before calling this.
*/
@@ -558,7 +565,6 @@ public class UserManagerService extends IUserManager.Stub {
mNextSerialNumber = MIN_USER_ID;
Bundle restrictions = new Bundle();
- initRestrictionsToDefaults(restrictions);
mUserRestrictions.append(UserHandle.USER_OWNER, restrictions);
updateUserIdsLocked();
@@ -608,11 +614,11 @@ public class UserManagerService extends IUserManager.Stub {
Bundle restrictions = mUserRestrictions.get(userInfo.id);
if (restrictions != null) {
serializer.startTag(null, TAG_RESTRICTIONS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_CONFIG_WIFI);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_MODIFY_ACCOUNTS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_INSTALL_APPS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_UNINSTALL_APPS);
- writeBoolean(serializer, restrictions, UserManager.ALLOW_CONFIG_LOCATION_ACCESS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
serializer.endTag(null, TAG_RESTRICTIONS);
}
serializer.endTag(null, TAG_USER);
@@ -676,7 +682,6 @@ public class UserManagerService extends IUserManager.Stub {
long lastLoggedInTime = 0L;
boolean partial = false;
Bundle restrictions = new Bundle();
- initRestrictionsToDefaults(restrictions);
FileInputStream fis = null;
try {
@@ -725,11 +730,11 @@ public class UserManagerService extends IUserManager.Stub {
name = parser.getText();
}
} else if (TAG_RESTRICTIONS.equals(tag)) {
- readBoolean(parser, restrictions, UserManager.ALLOW_CONFIG_WIFI);
- readBoolean(parser, restrictions, UserManager.ALLOW_MODIFY_ACCOUNTS);
- readBoolean(parser, restrictions, UserManager.ALLOW_INSTALL_APPS);
- readBoolean(parser, restrictions, UserManager.ALLOW_UNINSTALL_APPS);
- readBoolean(parser, restrictions, UserManager.ALLOW_CONFIG_LOCATION_ACCESS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
}
}
}
@@ -758,7 +763,9 @@ public class UserManagerService extends IUserManager.Stub {
private void readBoolean(XmlPullParser parser, Bundle restrictions,
String restrictionKey) {
String value = parser.getAttributeValue(null, restrictionKey);
- restrictions.putBoolean(restrictionKey, value == null ? true : Boolean.parseBoolean(value));
+ if (value != null) {
+ restrictions.putBoolean(restrictionKey, Boolean.parseBoolean(value));
+ }
}
private void writeBoolean(XmlSerializer xml, Bundle restrictions, String restrictionKey)
@@ -769,14 +776,6 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private void initRestrictionsToDefaults(Bundle restrictions) {
- restrictions.putBoolean(UserManager.ALLOW_CONFIG_WIFI, true);
- restrictions.putBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS, true);
- restrictions.putBoolean(UserManager.ALLOW_INSTALL_APPS, true);
- restrictions.putBoolean(UserManager.ALLOW_UNINSTALL_APPS, true);
- restrictions.putBoolean(UserManager.ALLOW_CONFIG_LOCATION_ACCESS, true);
- }
-
private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
String valueString = parser.getAttributeValue(null, attr);
if (valueString == null) return defaultValue;
@@ -823,7 +822,6 @@ public class UserManagerService extends IUserManager.Stub {
writeUserLocked(userInfo);
updateUserIdsLocked();
Bundle restrictions = new Bundle();
- initRestrictionsToDefaults(restrictions);
mUserRestrictions.append(userId, restrictions);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 7ef1485..2e309be 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -66,8 +66,8 @@ public class UserManagerTest extends AndroidTestCase {
&& !user.isPrimary()) {
found = true;
Bundle restrictions = mUserManager.getUserRestrictions(user.getUserHandle());
- assertTrue("New user should have ALLOW_CONFIG_WIFI =true by default",
- restrictions.getBoolean(UserManager.ALLOW_CONFIG_WIFI));
+ assertFalse("New user should have DISALLOW_CONFIG_WIFI =false by default",
+ restrictions.getBoolean(UserManager.DISALLOW_CONFIG_WIFI));
}
}
assertTrue(found);
@@ -147,13 +147,13 @@ public class UserManagerTest extends AndroidTestCase {
List<UserInfo> users = mUserManager.getUsers();
if (users.size() > 1) {
Bundle restrictions = new Bundle();
- restrictions.putBoolean(UserManager.ALLOW_INSTALL_APPS, false);
- restrictions.putBoolean(UserManager.ALLOW_CONFIG_WIFI, true);
+ restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true);
+ restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, false);
mUserManager.setUserRestrictions(restrictions, new UserHandle(users.get(1).id));
Bundle stored = mUserManager.getUserRestrictions(new UserHandle(users.get(1).id));
- assertEquals(stored.getBoolean(UserManager.ALLOW_CONFIG_WIFI), true);
- assertEquals(stored.getBoolean(UserManager.ALLOW_UNINSTALL_APPS), true);
- assertEquals(stored.getBoolean(UserManager.ALLOW_INSTALL_APPS), false);
+ assertEquals(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI), false);
+ assertEquals(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS), false);
+ assertEquals(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS), true);
}
}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index b57910f..c0a3bc1 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1677,12 +1677,12 @@ public class WifiStateMachine extends StateMachine {
setNetworkDetailedState(DetailedState.DISCONNECTED);
mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
- /* send event to CM & network change broadcast */
- sendNetworkStateChangeBroadcast(mLastBssid);
-
/* Clear network properties */
mLinkProperties.clear();
+ /* send event to CM & network change broadcast */
+ sendNetworkStateChangeBroadcast(mLastBssid);
+
/* Clear IP settings if the network used DHCP */
if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
mWifiConfigStore.clearLinkProperties(mLastNetworkId);