Merge "[ActivityManager] Fix IndexOutOfBoundsException" am: a61515720a
am: 6c8b91d8c2
* commit '6c8b91d8c2c657eb8a5b0afa00a3b93044447f56':
[ActivityManager] Fix IndexOutOfBoundsException
diff --git a/Android.mk b/Android.mk
index 791a70a..ce01c20 100644
--- a/Android.mk
+++ b/Android.mk
@@ -348,6 +348,7 @@
media/java/android/media/IRingtonePlayer.aidl \
media/java/android/media/IVolumeController.aidl \
media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \
+ media/java/android/media/midi/IBluetoothMidiService.aidl \
media/java/android/media/midi/IMidiDeviceListener.aidl \
media/java/android/media/midi/IMidiDeviceOpenCallback.aidl \
media/java/android/media/midi/IMidiDeviceServer.aidl \
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2cb3f39..ecd0050 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -689,6 +689,8 @@
private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_";
+ private static final String HAS_CURENT_PERMISSIONS_REQUEST_KEY =
+ "android:hasCurrentPermissionsRequest";
private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
@@ -797,6 +799,8 @@
SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK;
SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK;
+ private boolean mHasCurrentPermissionsRequest;
+
/** Return the intent that started this activity. */
public Intent getIntent() {
return mIntent;
@@ -1298,6 +1302,7 @@
onSaveInstanceState(outState);
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
+ storeHasCurrentPermissionRequest(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
@@ -1313,6 +1318,7 @@
final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
onSaveInstanceState(outState, outPersistentState);
saveManagedDialogs(outState);
+ storeHasCurrentPermissionRequest(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
", " + outPersistentState);
}
@@ -3811,8 +3817,15 @@
* @see #shouldShowRequestPermissionRationale(String)
*/
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
+ if (mHasCurrentPermissionsRequest) {
+ Log.w(TAG, "Can reqeust only one set of permissions at a time");
+ // Dispatch the callback with empty arrays which means a cancellation.
+ onRequestPermissionsResult(requestCode, new String[0], new int[0]);
+ return;
+ }
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
+ mHasCurrentPermissionsRequest = true;
}
/**
@@ -6234,12 +6247,14 @@
}
final void performCreate(Bundle icicle) {
+ restoreHasCurrentPermissionRequest(icicle);
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+ restoreHasCurrentPermissionRequest(icicle);
onCreate(icicle, persistentState);
mActivityTransitionState.readState(icicle);
performCreateCommon();
@@ -6418,6 +6433,19 @@
return mResumed;
}
+ private void storeHasCurrentPermissionRequest(Bundle bundle) {
+ if (bundle != null && mHasCurrentPermissionsRequest) {
+ bundle.putBoolean(HAS_CURENT_PERMISSIONS_REQUEST_KEY, true);
+ }
+ }
+
+ private void restoreHasCurrentPermissionRequest(Bundle bundle) {
+ if (bundle != null) {
+ mHasCurrentPermissionsRequest = bundle.getBoolean(
+ HAS_CURENT_PERMISSIONS_REQUEST_KEY, false);
+ }
+ }
+
void dispatchActivityResult(String who, int requestCode,
int resultCode, Intent data) {
if (false) Log.v(
@@ -6545,6 +6573,7 @@
}
private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
+ mHasCurrentPermissionsRequest = false;
// If the package installer crashed we may have not data - best effort.
String[] permissions = (data != null) ? data.getStringArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0adce5d..7cae745 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1619,7 +1619,8 @@
// System apps and apps demanding internal storage can't be moved
// anywhere else
if (app.isSystemApp()
- || app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+ || app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY
+ || app.installLocation == PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
return false;
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index d50483e..29e8dd4 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -460,9 +460,6 @@
// If set this fragment is being retained across the current config change.
boolean mRetaining;
- // If set this fragment's loaders are being retained across the current config change.
- boolean mRetainLoader;
-
// If set this fragment has menu items to contribute.
boolean mHasMenu;
@@ -2404,7 +2401,7 @@
mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
}
if (mLoaderManager != null) {
- if (mRetainLoader) {
+ if (mHost.getRetainLoaders()) {
mLoaderManager.doRetain();
} else {
mLoaderManager.doStop();
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 1b45137..28dadfa 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -341,7 +341,6 @@
*/
public void doLoaderStop(boolean retain) {
mHost.doLoaderStop(retain);
- mHost.mFragmentManager.setRetainLoader(retain);
}
/**
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 7b01307..13517e6 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -42,9 +42,14 @@
private final Handler mHandler;
final int mWindowAnimations;
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
+ /** The loader managers for individual fragments [i.e. Fragment#getLoaderManager()] */
private ArrayMap<String, LoaderManager> mAllLoaderManagers;
+ /** Whether or not fragment loaders should retain their state */
+ private boolean mRetainLoaders;
+ /** The loader manger for the fragment host [i.e. Activity#getLoaderManager()] */
private LoaderManagerImpl mLoaderManager;
private boolean mCheckedForLoaderManager;
+ /** Whether or not the fragment host loader manager was started */
private boolean mLoadersStarted;
public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
@@ -166,6 +171,10 @@
return true;
}
+ boolean getRetainLoaders() {
+ return mRetainLoaders;
+ }
+
Activity getActivity() {
return mActivity;
}
@@ -217,6 +226,8 @@
}
void doLoaderStop(boolean retain) {
+ mRetainLoaders = retain;
+
if (mLoaderManager == null) {
return;
}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 51d6132..696ccdb 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -869,17 +869,6 @@
}
}
- void setRetainLoader(boolean retain) {
- if (mActive != null) {
- for (int i=0; i<mActive.size(); i++) {
- Fragment f = mActive.get(i);
- if (f != null) {
- f.mRetainLoader = retain;
- }
- }
- }
- }
-
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
if (DEBUG && false) Log.v(TAG, "moveToState: " + f
@@ -2221,6 +2210,7 @@
// This fragment was retained from a previous instance; get it
// going now.
fragment.mInLayout = true;
+ fragment.mHost = mHost;
// If this fragment is newly instantiated (either right now, or
// from last saved state), then give it the attributes to
// initialize itself.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4c7dd10..758b6ff 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -643,20 +643,18 @@
/**
* Open a private file associated with this Context's application package
- * for writing. Creates the file if it doesn't already exist.
- *
- * <p>No permissions are required to invoke this method, since it uses internal
- * storage.
+ * for writing. Creates the file if it doesn't already exist.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write the returned file.
*
* @param name The name of the file to open; can not contain path
- * separators.
- * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the
- * default operation, {@link #MODE_APPEND} to append to an existing file,
- * {@link #MODE_WORLD_READABLE} and {@link #MODE_WORLD_WRITEABLE} to control
- * permissions.
- *
+ * separators.
+ * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the
+ * default operation, {@link #MODE_APPEND} to append to an
+ * existing file, {@link #MODE_WORLD_READABLE} and
+ * {@link #MODE_WORLD_WRITEABLE} to control permissions.
* @return The resulting {@link FileOutputStream}.
- *
* @see #MODE_APPEND
* @see #MODE_PRIVATE
* @see #MODE_WORLD_READABLE
@@ -689,6 +687,9 @@
/**
* Returns the absolute path on the filesystem where a file created with
* {@link #openFileOutput} is stored.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
*
* @param name The name of the file for which you would like to get
* its path.
@@ -702,14 +703,16 @@
public abstract File getFileStreamPath(String name);
/**
- * Returns the absolute path to the directory on the filesystem where
- * files created with {@link #openFileOutput} are stored.
- *
- * <p>No permissions are required to read or write to the returned path, since this
- * path is internal storage.
+ * Returns the absolute path to the directory on the filesystem where files
+ * created with {@link #openFileOutput} are stored.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
*
* @return The path of the directory holding application files.
- *
* @see #openFileOutput
* @see #getFileStreamPath
* @see #getDir
@@ -718,17 +721,19 @@
/**
* Returns the absolute path to the directory on the filesystem similar to
- * {@link #getFilesDir()}. The difference is that files placed under this
- * directory will be excluded from automatic backup to remote storage. See
+ * {@link #getFilesDir()}. The difference is that files placed under this
+ * directory will be excluded from automatic backup to remote storage. See
* {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
* of the automatic backup mechanism in Android.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
*
- * <p>No permissions are required to read or write to the returned path, since this
- * path is internal storage.
- *
- * @return The path of the directory holding application files that will not be
- * automatically backed up to remote storage.
- *
+ * @return The path of the directory holding application files that will not
+ * be automatically backed up to remote storage.
* @see #openFileOutput
* @see #getFileStreamPath
* @see #getDir
@@ -737,200 +742,256 @@
public abstract File getNoBackupFilesDir();
/**
- * Returns the absolute path to the directory on the primary external filesystem
- * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
- * Environment.getExternalStorageDirectory()}) where the application can
- * place persistent files it owns. These files are internal to the
- * applications, and not typically visible to the user as media.
- *
- * <p>This is like {@link #getFilesDir()} in that these
- * files will be deleted when the application is uninstalled, however there
- * are some important differences:
- *
+ * Returns the absolute path to the directory on the primary shared/external
+ * storage device where the application can place persistent files it owns.
+ * These files are internal to the applications, and not typically visible
+ * to the user as media.
+ * <p>
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
* <ul>
- * <li>External files are not always available: they will disappear if the
- * user mounts the external storage on a computer or removes it. See the
- * APIs on {@link android.os.Environment} for information in the storage state.
- * <li>There is no security enforced with these files. For example, any application
- * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
* these files.
* </ul>
- *
- * <p>Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+ * <p>
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directories returned
+ * by {@link #getFilesDir()}, etc.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
* are required to read or write to the returned path; it's always
- * accessible to the calling app. This only applies to paths generated for
- * package name of the calling application. To access paths belonging
- * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
- * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
- *
- * <p>On devices with multiple users (as described by {@link UserManager}),
- * each user has their own isolated external storage. Applications only
- * have access to the external storage for the user they're running as.</p>
- *
- * <p>Here is an example of typical code to manipulate a file in
- * an application's private storage:</p>
- *
+ * accessible to the calling app. This only applies to paths generated for
+ * package name of the calling application. To access paths belonging to
+ * other packages,
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+ * <p>
+ * On devices with multiple users (as described by {@link UserManager}),
+ * each user has their own isolated shared storage. Applications only have
+ * access to the shared storage for the user they're running as.
+ * <p>
+ * The returned path may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
+ * <p>
+ * Here is an example of typical code to manipulate a file in an
+ * application's shared storage:
+ * </p>
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* private_file}
- *
- * <p>If you supply a non-null <var>type</var> to this function, the returned
- * file will be a path to a sub-directory of the given type. Though these files
- * are not automatically scanned by the media scanner, you can explicitly
- * add them to the media database with
- * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[],
- * android.media.MediaScannerConnection.OnScanCompletedListener)
- * MediaScannerConnection.scanFile}.
- * Note that this is not the same as
+ * <p>
+ * If you supply a non-null <var>type</var> to this function, the returned
+ * file will be a path to a sub-directory of the given type. Though these
+ * files are not automatically scanned by the media scanner, you can
+ * explicitly add them to the media database with
+ * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[], android.media.MediaScannerConnection.OnScanCompletedListener)
+ * MediaScannerConnection.scanFile}. Note that this is not the same as
* {@link android.os.Environment#getExternalStoragePublicDirectory
* Environment.getExternalStoragePublicDirectory()}, which provides
- * directories of media shared by all applications. The
- * directories returned here are
- * owned by the application, and their contents will be removed when the
- * application is uninstalled. Unlike
+ * directories of media shared by all applications. The directories returned
+ * here are owned by the application, and their contents will be removed
+ * when the application is uninstalled. Unlike
* {@link android.os.Environment#getExternalStoragePublicDirectory
- * Environment.getExternalStoragePublicDirectory()}, the directory
- * returned here will be automatically created for you.
- *
- * <p>Here is an example of typical code to manipulate a picture in
- * an application's private storage and add it to the media database:</p>
- *
+ * Environment.getExternalStoragePublicDirectory()}, the directory returned
+ * here will be automatically created for you.
+ * <p>
+ * Here is an example of typical code to manipulate a picture in an
+ * application's shared storage and add it to the media database:
+ * </p>
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* private_picture}
*
- * @param type The type of files directory to return. May be null for
- * the root of the files directory or one of
- * the following Environment constants for a subdirectory:
- * {@link android.os.Environment#DIRECTORY_MUSIC},
- * {@link android.os.Environment#DIRECTORY_PODCASTS},
- * {@link android.os.Environment#DIRECTORY_RINGTONES},
- * {@link android.os.Environment#DIRECTORY_ALARMS},
- * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
- * {@link android.os.Environment#DIRECTORY_PICTURES}, or
- * {@link android.os.Environment#DIRECTORY_MOVIES}.
- *
- * @return The path of the directory holding application files
- * on external storage. Returns null if external storage is not currently
- * mounted so it could not ensure the path exists; you will need to call
- * this method again when it is available.
- *
+ * @param type The type of files directory to return. May be {@code null}
+ * for the root of the files directory or one of the following
+ * constants for a subdirectory:
+ * {@link android.os.Environment#DIRECTORY_MUSIC},
+ * {@link android.os.Environment#DIRECTORY_PODCASTS},
+ * {@link android.os.Environment#DIRECTORY_RINGTONES},
+ * {@link android.os.Environment#DIRECTORY_ALARMS},
+ * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
+ * {@link android.os.Environment#DIRECTORY_PICTURES}, or
+ * {@link android.os.Environment#DIRECTORY_MOVIES}.
+ * @return the absolute path to application-specific directory. May return
+ * {@code null} if shared storage is not currently available.
* @see #getFilesDir
- * @see android.os.Environment#getExternalStoragePublicDirectory
+ * @see #getExternalFilesDirs(String)
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
*/
@Nullable
public abstract File getExternalFilesDir(@Nullable String type);
/**
* Returns absolute paths to application-specific directories on all
- * external storage devices where the application can place persistent files
- * it owns. These files are internal to the application, and not typically
- * visible to the user as media.
+ * shared/external storage devices where the application can place
+ * persistent files it owns. These files are internal to the application,
+ * and not typically visible to the user as media.
* <p>
- * This is like {@link #getFilesDir()} in that these files will be deleted when
- * the application is uninstalled, however there are some important differences:
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
* <ul>
- * <li>External files are not always available: they will disappear if the
- * user mounts the external storage on a computer or removes it.
- * <li>There is no security enforced with these files.
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
* </ul>
* <p>
- * External storage devices returned here are considered a permanent part of
- * the device, including both emulated external storage and physical media
- * slots, such as SD cards in a battery compartment. The returned paths do
- * not include transient devices, such as USB flash drives.
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directories returned
+ * by {@link #getFilesDir()}, etc.
* <p>
- * An application may store data on any or all of the returned devices. For
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
* example, an app may choose to store large files on the device with the
* most available space, as measured by {@link StatFs}.
* <p>
- * No permissions are required to read or write to the returned paths; they
- * are always accessible to the calling app. Write access outside of these
- * paths on secondary external storage devices is not available.
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
* <p>
- * The first path returned is the same as {@link #getExternalFilesDir(String)}.
- * Returned paths may be {@code null} if a storage device is unavailable.
+ * The returned path may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
*
+ * @param type The type of files directory to return. May be {@code null}
+ * for the root of the files directory or one of the following
+ * constants for a subdirectory:
+ * {@link android.os.Environment#DIRECTORY_MUSIC},
+ * {@link android.os.Environment#DIRECTORY_PODCASTS},
+ * {@link android.os.Environment#DIRECTORY_RINGTONES},
+ * {@link android.os.Environment#DIRECTORY_ALARMS},
+ * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
+ * {@link android.os.Environment#DIRECTORY_PICTURES}, or
+ * {@link android.os.Environment#DIRECTORY_MOVIES}.
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available. The first path returned is the same as
+ * {@link #getExternalFilesDir(String)}.
* @see #getExternalFilesDir(String)
* @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
*/
public abstract File[] getExternalFilesDirs(String type);
/**
- * Return the primary external storage directory where this application's OBB
- * files (if there are any) can be found. Note if the application does not have
- * any OBB files, this directory may not exist.
+ * Return the primary shared/external storage directory where this
+ * application's OBB files (if there are any) can be found. Note if the
+ * application does not have any OBB files, this directory may not exist.
* <p>
- * This is like {@link #getFilesDir()} in that these files will be deleted when
- * the application is uninstalled, however there are some important differences:
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
* <ul>
- * <li>External files are not always available: they will disappear if the
- * user mounts the external storage on a computer or removes it.
- * <li>There is no security enforced with these files. For example, any application
- * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
* these files.
* </ul>
* <p>
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
* are required to read or write to the returned path; it's always
- * accessible to the calling app. This only applies to paths generated for
- * package name of the calling application. To access paths belonging
- * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
- * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+ * accessible to the calling app. This only applies to paths generated for
+ * package name of the calling application. To access paths belonging to
+ * other packages,
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
* <p>
* On devices with multiple users (as described by {@link UserManager}),
* multiple users may share the same OBB storage location. Applications
* should ensure that multiple instances running under different users don't
* interfere with each other.
+ *
+ * @return the absolute path to application-specific directory. May return
+ * {@code null} if shared storage is not currently available.
+ * @see #getObbDirs()
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
*/
public abstract File getObbDir();
/**
* Returns absolute paths to application-specific directories on all
- * external storage devices where the application's OBB files (if there are
- * any) can be found. Note if the application does not have any OBB files,
- * these directories may not exist.
+ * shared/external storage devices where the application's OBB files (if
+ * there are any) can be found. Note if the application does not have any
+ * OBB files, these directories may not exist.
* <p>
- * This is like {@link #getFilesDir()} in that these files will be deleted when
- * the application is uninstalled, however there are some important differences:
+ * This is like {@link #getFilesDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
* <ul>
- * <li>External files are not always available: they will disappear if the
- * user mounts the external storage on a computer or removes it.
- * <li>There is no security enforced with these files.
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
* </ul>
* <p>
- * External storage devices returned here are considered a permanent part of
- * the device, including both emulated external storage and physical media
- * slots, such as SD cards in a battery compartment. The returned paths do
- * not include transient devices, such as USB flash drives.
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
* <p>
- * An application may store data on any or all of the returned devices. For
+ * An application may store data on any or all of the returned devices. For
* example, an app may choose to store large files on the device with the
* most available space, as measured by {@link StatFs}.
* <p>
- * No permissions are required to read or write to the returned paths; they
- * are always accessible to the calling app. Write access outside of these
- * paths on secondary external storage devices is not available.
- * <p>
- * The first path returned is the same as {@link #getObbDir()}.
- * Returned paths may be {@code null} if a storage device is unavailable.
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
*
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available. The first path returned is the same as
+ * {@link #getObbDir()}
* @see #getObbDir()
* @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
*/
public abstract File[] getObbDirs();
/**
- * Returns the absolute path to the application specific cache directory
- * on the filesystem. These files will be ones that get deleted first when the
- * device runs low on storage.
- * There is no guarantee when these files will be deleted.
- *
+ * Returns the absolute path to the application specific cache directory on
+ * the filesystem. These files will be ones that get deleted first when the
+ * device runs low on storage. There is no guarantee when these files will
+ * be deleted.
+ * <p>
* <strong>Note: you should not <em>rely</em> on the system deleting these
* files for you; you should always have a reasonable maximum, such as 1 MB,
* for the amount of space you consume with cache files, and prune those
* files when exceeding that space.</strong>
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * Apps require no extra permissions to read or write to the returned path,
+ * since this path lives in their private storage.
*
* @return The path of the directory holding application cache files.
- *
* @see #openFileOutput
* @see #getFileStreamPath
* @see #getDir
@@ -946,6 +1007,9 @@
* This location is optimal for storing compiled or optimized code generated
* by your application at runtime.
* <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
* Apps require no extra permissions to read or write to the returned path,
* since this path lives in their private storage.
*
@@ -954,120 +1018,161 @@
public abstract File getCodeCacheDir();
/**
- * Returns the absolute path to the directory on the primary external filesystem
- * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
- * Environment.getExternalStorageDirectory()} where the application can
- * place cache files it owns. These files are internal to the application, and
- * not typically visible to the user as media.
- *
- * <p>This is like {@link #getCacheDir()} in that these
- * files will be deleted when the application is uninstalled, however there
- * are some important differences:
- *
+ * Returns absolute path to application-specific directory on the primary
+ * shared/external storage device where the application can place cache
+ * files it owns. These files are internal to the application, and not
+ * typically visible to the user as media.
+ * <p>
+ * This is like {@link #getCacheDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
* <ul>
- * <li>The platform does not always monitor the space available in external
- * storage, and thus may not automatically delete these files. Currently
- * the only time files here will be deleted by the platform is when running
- * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
- * {@link android.os.Environment#isExternalStorageEmulated()
- * Environment.isExternalStorageEmulated()} returns true. Note that you should
- * be managing the maximum space you will use for these anyway, just like
- * with {@link #getCacheDir()}.
- * <li>External files are not always available: they will disappear if the
- * user mounts the external storage on a computer or removes it. See the
- * APIs on {@link android.os.Environment} for information in the storage state.
- * <li>There is no security enforced with these files. For example, any application
- * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * <li>The platform does not always monitor the space available in shared
+ * storage, and thus may not automatically delete these files. Apps should
+ * always manage the maximum space used in this location. Currently the only
+ * time files here will be deleted by the platform is when running on
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
+ * {@link Environment#isExternalStorageEmulated(File)} returns true.
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
* these files.
* </ul>
- *
- * <p>Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+ * <p>
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directory returned by
+ * {@link #getCacheDir()}.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
* are required to read or write to the returned path; it's always
- * accessible to the calling app. This only applies to paths generated for
- * package name of the calling application. To access paths belonging
- * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
- * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+ * accessible to the calling app. This only applies to paths generated for
+ * package name of the calling application. To access paths belonging to
+ * other packages,
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} and/or
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+ * <p>
+ * On devices with multiple users (as described by {@link UserManager}),
+ * each user has their own isolated shared storage. Applications only have
+ * access to the shared storage for the user they're running as.
+ * <p>
+ * The returned path may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
*
- * <p>On devices with multiple users (as described by {@link UserManager}),
- * each user has their own isolated external storage. Applications only
- * have access to the external storage for the user they're running as.</p>
- *
- * @return The path of the directory holding application cache files
- * on external storage. Returns null if external storage is not currently
- * mounted so it could not ensure the path exists; you will need to call
- * this method again when it is available.
- *
+ * @return the absolute path to application-specific directory. May return
+ * {@code null} if shared storage is not currently available.
* @see #getCacheDir
+ * @see #getExternalCacheDirs()
+ * @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
*/
@Nullable
public abstract File getExternalCacheDir();
/**
* Returns absolute paths to application-specific directories on all
- * external storage devices where the application can place cache files it
- * owns. These files are internal to the application, and not typically
- * visible to the user as media.
+ * shared/external storage devices where the application can place cache
+ * files it owns. These files are internal to the application, and not
+ * typically visible to the user as media.
* <p>
- * This is like {@link #getCacheDir()} in that these files will be deleted when
- * the application is uninstalled, however there are some important differences:
+ * This is like {@link #getCacheDir()} in that these files will be deleted
+ * when the application is uninstalled, however there are some important
+ * differences:
* <ul>
- * <li>External files are not always available: they will disappear if the
- * user mounts the external storage on a computer or removes it.
- * <li>There is no security enforced with these files.
+ * <li>The platform does not always monitor the space available in shared
+ * storage, and thus may not automatically delete these files. Apps should
+ * always manage the maximum space used in this location. Currently the only
+ * time files here will be deleted by the platform is when running on
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
+ * {@link Environment#isExternalStorageEmulated(File)} returns true.
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
* </ul>
* <p>
- * External storage devices returned here are considered a permanent part of
- * the device, including both emulated external storage and physical media
- * slots, such as SD cards in a battery compartment. The returned paths do
- * not include transient devices, such as USB flash drives.
+ * If a shared storage device is emulated (as determined by
+ * {@link Environment#isExternalStorageEmulated(File)}), it's contents are
+ * backed by a private user data partition, which means there is little
+ * benefit to storing data here instead of the private directory returned by
+ * {@link #getCacheDir()}.
* <p>
- * An application may store data on any or all of the returned devices. For
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
* example, an app may choose to store large files on the device with the
* most available space, as measured by {@link StatFs}.
* <p>
- * No permissions are required to read or write to the returned paths; they
- * are always accessible to the calling app. Write access outside of these
- * paths on secondary external storage devices is not available.
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
* <p>
- * The first path returned is the same as {@link #getExternalCacheDir()}.
- * Returned paths may be {@code null} if a storage device is unavailable.
+ * The returned paths may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
*
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available. The first path returned is the same as
+ * {@link #getExternalCacheDir()}.
* @see #getExternalCacheDir()
* @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
*/
public abstract File[] getExternalCacheDirs();
/**
* Returns absolute paths to application-specific directories on all
- * external storage devices where the application can place media files.
- * These files are scanned and made available to other apps through
+ * shared/external storage devices where the application can place media
+ * files. These files are scanned and made available to other apps through
* {@link MediaStore}.
* <p>
* This is like {@link #getExternalFilesDirs} in that these files will be
* deleted when the application is uninstalled, however there are some
* important differences:
* <ul>
- * <li>External files are not always available: they will disappear if the
- * user mounts the external storage on a computer or removes it.
- * <li>There is no security enforced with these files.
+ * <li>Shared storage may not always be available, since removable media can
+ * be ejected by the user. Media state can be checked using
+ * {@link Environment#getExternalStorageState(File)}.
+ * <li>There is no security enforced with these files. For example, any
+ * application holding
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+ * these files.
* </ul>
* <p>
- * External storage devices returned here are considered a permanent part of
- * the device, including both emulated external storage and physical media
- * slots, such as SD cards in a battery compartment. The returned paths do
- * not include transient devices, such as USB flash drives.
+ * Shared storage devices returned here are considered a stable part of the
+ * device, including physical media slots under a protective cover. The
+ * returned paths do not include transient devices, such as USB flash drives
+ * connected to handheld devices.
* <p>
* An application may store data on any or all of the returned devices. For
* example, an app may choose to store large files on the device with the
* most available space, as measured by {@link StatFs}.
* <p>
- * No permissions are required to read or write to the returned paths; they
- * are always accessible to the calling app. Write access outside of these
- * paths on secondary external storage devices is not available.
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path. Write access outside of these paths
+ * on secondary external storage devices is not available.
* <p>
- * Returned paths may be {@code null} if a storage device is unavailable.
+ * The returned paths may change over time if different shared storage media
+ * is inserted, so only relative paths should be persisted.
*
+ * @return the absolute paths to application-specific directories. Some
+ * individual paths may be {@code null} if that shared storage is
+ * not currently available.
* @see Environment#getExternalStorageState(File)
+ * @see Environment#isExternalStorageEmulated(File)
+ * @see Environment#isExternalStorageRemovable(File)
*/
public abstract File[] getExternalMediaDirs();
@@ -1090,6 +1195,12 @@
* created through a File object will only be accessible by your own
* application; you can only set the mode of the entire directory, not
* of individual files.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * Apps require no extra permissions to read or write to the returned path,
+ * since this path lives in their private storage.
*
* @param name Name of the directory to retrieve. This is a directory
* that is created as part of your application data.
@@ -1173,6 +1284,9 @@
/**
* Returns the absolute path on the filesystem where a database created with
* {@link #openOrCreateDatabase} is stored.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
*
* @param name The name of the database for which you would like to get
* its path.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index dac17d5..3d0c572 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3159,6 +3159,13 @@
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_HOME = "android.intent.category.HOME";
/**
+ * This is the home activity that is displayed when the device is finished setting up and ready
+ * for use.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_HOME_MAIN = "android.intent.category.HOME_MAIN";
+ /**
* This is the setup wizard activity, that is the first activity that is displayed
* when the user sets up the device for the first time.
* @hide
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 3efd89a..0441ccc 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -21,6 +21,8 @@
import android.os.Parcel;
import android.accounts.Account;
+import java.util.Objects;
+
/**
* Value type that contains information about a periodic sync.
*/
@@ -144,7 +146,9 @@
if (!b2.containsKey(key)) {
return false;
}
- if (!b1.get(key).equals(b2.get(key))) {
+ // Null check. According to ContentResolver#validateSyncExtrasBundle null-valued keys
+ // are allowed in the bundle.
+ if (!Objects.equals(b1.get(key), b2.get(key))) {
return false;
}
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 43cc63b..e798eb8 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -33,12 +33,16 @@
*/
public class ActivityInfo extends ComponentInfo
implements Parcelable {
+
+ // NOTE: When adding new data members be sure to update the copy-constructor, Parcel
+ // constructor, and writeToParcel.
+
/**
* A style resource identifier (in the package's resources) of this
* activity's theme. From the "theme" attribute or, if not set, 0.
*/
public int theme;
-
+
/**
* Constant corresponding to <code>standard</code> in
* the {@link android.R.attr#launchMode} attribute.
@@ -705,6 +709,7 @@
super(orig);
theme = orig.theme;
launchMode = orig.launchMode;
+ documentLaunchMode = orig.documentLaunchMode;
permission = orig.permission;
taskAffinity = orig.taskAffinity;
targetActivity = orig.targetActivity;
@@ -780,6 +785,7 @@
super.writeToParcel(dest, parcelableFlags);
dest.writeInt(theme);
dest.writeInt(launchMode);
+ dest.writeInt(documentLaunchMode);
dest.writeString(permission);
dest.writeString(taskAffinity);
dest.writeString(targetActivity);
@@ -809,6 +815,7 @@
super(source);
theme = source.readInt();
launchMode = source.readInt();
+ documentLaunchMode = source.readInt();
permission = source.readString();
taskAffinity = source.readString();
targetActivity = source.readString();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7032c9a..99bd390 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -894,6 +894,7 @@
}
pkg.volumeUuid = volumeUuid;
+ pkg.applicationInfo.volumeUuid = volumeUuid;
pkg.baseCodePath = apkPath;
pkg.mSignatures = null;
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 30cdfd3..2fe8fb6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -78,14 +78,14 @@
sSensorModuleInitialized = true;
nativeClassInit();
}
- }
- // initialize the sensor list
- for (int index = 0;;++index) {
- Sensor sensor = new Sensor();
- if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
- mFullSensorsList.add(sensor);
- mHandleToSensor.append(sensor.getHandle(), sensor);
+ // initialize the sensor list
+ for (int index = 0;;++index) {
+ Sensor sensor = new Sensor();
+ if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ mFullSensorsList.add(sensor);
+ mHandleToSensor.append(sensor.getHandle(), sensor);
+ }
}
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index c8b45c7..3601b39 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -61,6 +61,8 @@
// Registers an input devices changed listener.
void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
+ // Queries whether the device is currently in tablet mode
+ int isInTabletMode();
// Registers a tablet mode change listener
void registerTabletModeChangedListener(ITabletModeChangedListener listener);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a754d6b..618864f 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,6 +19,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
+import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
@@ -39,6 +40,8 @@
import android.view.InputDevice;
import android.view.InputEvent;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -179,6 +182,31 @@
*/
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SWITCH_STATE_UNKNOWN, SWITCH_STATE_OFF, SWITCH_STATE_ON})
+ public @interface SwitchState {}
+
+ /**
+ * Switch State: Unknown.
+ *
+ * The system has yet to report a valid value for the switch.
+ * @hide
+ */
+ public static final int SWITCH_STATE_UNKNOWN = -1;
+
+ /**
+ * Switch State: Off.
+ * @hide
+ */
+ public static final int SWITCH_STATE_OFF = 0;
+
+ /**
+ * Switch State: On.
+ * @hide
+ */
+ public static final int SWITCH_STATE_ON = 1;
+
private InputManager(IInputManager im) {
mIm = im;
}
@@ -339,6 +367,23 @@
}
/**
+ * Queries whether the device is in tablet mode.
+ *
+ * @return The tablet switch state which is one of {@link #SWITCH_STATE_UNKNOWN},
+ * {@link #SWITCH_STATE_OFF} or {@link #SWITCH_STATE_ON}.
+ * @hide
+ */
+ @SwitchState
+ public int isInTabletMode() {
+ try {
+ return mIm.isInTabletMode();
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get tablet mode state", ex);
+ return SWITCH_STATE_UNKNOWN;
+ }
+ }
+
+ /**
* Register a tablet mode changed listener.
*
* @param listener The listener to register.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 9a2a241..4d9b759 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -895,8 +895,12 @@
* Tells the underlying networking system that the caller wants to
* begin using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
* @param networkType specifies which network the request pertains to
* @param feature the name of the feature to be used
* @return an integer value representing the outcome of the request.
@@ -946,8 +950,12 @@
* Tells the underlying networking system that the caller is finished
* using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
* @param networkType specifies which network the request pertains to
* @param feature the name of the feature that is no longer needed
* @return an integer value representing the outcome of the request.
@@ -1333,8 +1341,12 @@
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
* @param networkType the type of the network over which traffic to the specified
* host is to be routed
* @param hostAddress the IP address of the host to which the route is desired
@@ -1354,8 +1366,12 @@
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
* @param networkType the type of the network over which traffic to the specified
* host is to be routed
* @param hostAddress the IP address of the host to which the route is desired
@@ -1555,6 +1571,13 @@
return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ /** {@hide} */
+ public static final void enforceChangePermission(Context context) {
+ int uid = Binder.getCallingUid();
+ Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
+ .getPackageNameForUid(context, uid), true /* throwException */);
+ }
+
/** {@hide */
public static final void enforceTetherChangePermission(Context context) {
if (context.getResources().getStringArray(
@@ -1565,8 +1588,8 @@
android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService");
} else {
int uid = Binder.getCallingUid();
- Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
- .getPackageNameForUid(context, uid), true);
+ Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
+ .getPackageNameForUid(context, uid), true /* throwException */);
}
}
@@ -1671,8 +1694,11 @@
* allowed between the tethered devices and this device, though upstream net
* access will of course fail until an upstream network interface becomes
* active.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
*
* @param iface the interface name to tether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -1689,8 +1715,11 @@
/**
* Stop tethering the named interface.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
*
* @param iface the interface name to untether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -1790,8 +1819,11 @@
* attempt to switch to Rndis and subsequently tether the resulting
* interface on {@code true} or turn off tethering and switch off
* Rndis on {@code false}.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
*
* @param enable a boolean - {@code true} to enable tethering
* @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -2461,8 +2493,11 @@
* network may never attain, and whether a network will attain these states
* is unknown prior to bringing up the network so the framework does not
* know how to go about satisfing a request with these capabilities.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} to be utilized for this
@@ -2484,8 +2519,12 @@
* network is not found within the given time (in milliseconds) the
* {@link NetworkCallback#unavailable} callback is called. The request must
* still be released normally by calling {@link releaseNetworkRequest}.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The callbacks to be utilized for this request. Note
* the callbacks must not be shared - they uniquely specify
@@ -2558,8 +2597,12 @@
* network may never attain, and whether a network will attain these states
* is unknown prior to bringing up the network so the framework does not
* know how to go about satisfing a request with these capabilities.
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
* @param request {@link NetworkRequest} describing this request.
* @param operation Action to perform when the network is available (corresponds
* to the {@link NetworkCallback#onAvailable} call. Typically
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index 87c063f..97bd5d2 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -21,7 +21,6 @@
import android.text.TextUtils;
import android.util.Log;
-import java.net.InetAddress;
import java.net.Inet4Address;
import java.util.Objects;
@@ -34,7 +33,7 @@
public class DhcpResults extends StaticIpConfiguration {
private static final String TAG = "DhcpResults";
- public InetAddress serverAddress;
+ public Inet4Address serverAddress;
/** Vendor specific information (from RFC 2132). */
public String vendorInfo;
@@ -142,7 +141,7 @@
private static void readFromParcel(DhcpResults dhcpResults, Parcel in) {
StaticIpConfiguration.readFromParcel(dhcpResults, in);
dhcpResults.leaseDuration = in.readInt();
- dhcpResults.serverAddress = NetworkUtils.unparcelInetAddress(in);
+ dhcpResults.serverAddress = (Inet4Address) NetworkUtils.unparcelInetAddress(in);
dhcpResults.vendorInfo = in.readString();
}
@@ -183,8 +182,8 @@
public boolean setServerAddress(String addrString) {
try {
- serverAddress = NetworkUtils.numericToInetAddress(addrString);
- } catch (IllegalArgumentException e) {
+ serverAddress = (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
+ } catch (IllegalArgumentException|ClassCastException e) {
Log.e(TAG, "setServerAddress failed with addrString " + addrString);
return true;
}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 02dae8a..eab22b8 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -51,12 +51,13 @@
public static final int POLICY_ALLOW_BACKGROUND_BATTERY_SAVE = 0x2;
/* RULE_* are not masks and they must be exclusive */
+ public static final int RULE_UNKNOWN = -1;
/** All network traffic should be allowed. */
- public static final int RULE_ALLOW_ALL = 0x0;
+ public static final int RULE_ALLOW_ALL = 0;
/** Reject traffic on metered networks. */
- public static final int RULE_REJECT_METERED = 0x1;
+ public static final int RULE_REJECT_METERED = 1;
/** Reject traffic on all networks. */
- public static final int RULE_REJECT_ALL = 0x2;
+ public static final int RULE_REJECT_ALL = 2;
public static final int FIREWALL_RULE_DEFAULT = 0;
public static final int FIREWALL_RULE_ALLOW = 1;
@@ -375,25 +376,4 @@
// nothing found above; we can apply policy to UID
return true;
}
-
- /** {@hide} */
- public static void dumpPolicy(PrintWriter fout, int policy) {
- fout.write("[");
- if ((policy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
- fout.write("REJECT_METERED_BACKGROUND");
- }
- fout.write("]");
- }
-
- /** {@hide} */
- public static void dumpRules(PrintWriter fout, int rules) {
- fout.write("[");
- if ((rules & RULE_REJECT_METERED) != 0) {
- fout.write("REJECT_METERED");
- } else if ((rules & RULE_REJECT_ALL) != 0) {
- fout.write("REJECT_ALL");
- }
- fout.write("]");
- }
-
}
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 57eef83..b7a411e 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -288,7 +288,8 @@
} else {
final boolean matchesType = (sForceAllNetworkTypes
|| contains(DATA_USAGE_NETWORK_TYPES, ident.mType));
- return matchesType && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
+ return matchesType && !ArrayUtils.isEmpty(mMatchSubscriberIds)
+ && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
}
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 2374899..862f4c4 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -577,7 +577,7 @@
public static final int KITKAT = 19;
/**
- * Android 4.4W: KitKat for watches, snacks on the run.
+ * June 2014: Android 4.4W. KitKat for watches, snacks on the run.
*
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
@@ -595,7 +595,7 @@
public static final int L = 21;
/**
- * Lollipop. A flat one with beautiful shadows. But still tasty.
+ * November 2014: Lollipop. A flat one with beautiful shadows. But still tasty.
*
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
@@ -626,12 +626,38 @@
public static final int LOLLIPOP = 21;
/**
- * Lollipop with an extra sugar coating on the outside!
+ * March 2015: Lollipop with an extra sugar coating on the outside!
*/
public static final int LOLLIPOP_MR1 = 22;
/**
- * M comes after L.
+ * M is for Marshmallow!
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li> Runtime permissions. Dangerous permissions are no longer granted at
+ * install time, but must be requested by the application at runtime through
+ * {@link android.app.Activity#requestPermissions}.</li>
+ * <li> Bluetooth and Wi-Fi scanning now requires holding the location permission.</li>
+ * <li> {@link android.app.AlarmManager#setTimeZone AlarmManager.setTimeZone} will fail if
+ * the given timezone is non-Olson.</li>
+ * <li> Activity transitions will only return shared
+ * elements mapped in the returned view hierarchy back to the calling activity.</li>
+ * <li> {@link android.view.View} allows a number of behaviors that may break
+ * existing apps: Canvas throws an exception if restore() is called too many times,
+ * widgets may return a hint size when returning UNSPECIFIED measure specs, and it
+ * will respect the attributes {@link android.R.attr#foreground},
+ * {@link android.R.attr#foregroundGravity}, {@link android.R.attr#foregroundTint}, and
+ * {@link android.R.attr#foregroundTintMode}.</li>
+ * <li> {@link android.view.MotionEvent#getButtonState MotionEvent.getButtonState}
+ * will no longer report {@link android.view.MotionEvent#BUTTON_PRIMARY}
+ * and {@link android.view.MotionEvent#BUTTON_SECONDARY} as synonyms for
+ * {@link android.view.MotionEvent#BUTTON_STYLUS_PRIMARY} and
+ * {@link android.view.MotionEvent#BUTTON_STYLUS_SECONDARY}.</li>
+ * <li> {@link android.widget.ScrollView} now respects the layout param margins
+ * when measuring.</li>
+ * </ul>
*/
public static final int M = 23;
}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 97b85e2..fdd34f5 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -471,7 +471,7 @@
* </tbody>
* </table>
*/
- public String getMemoryStat(String statName) {
+ public String getMemoryStat(String statName) {
switch(statName) {
case "summary.java-heap":
return Integer.toString(getSummaryJavaHeap());
@@ -1538,7 +1538,13 @@
/**
* Retrieves information about this processes memory usages. This information is broken down by
- * how much is in use by dalivk, the native heap, and everything else.
+ * how much is in use by dalvik, the native heap, and everything else.
+ *
+ * <p><b>Note:</b> this method directly retrieves memory information for the give process
+ * from low-level data available to it. It may not be able to retrieve information about
+ * some protected allocations, such as graphics. If you want to be sure you can see
+ * all information about allocations by the process, use instead
+ * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])}.</p>
*/
public static native void getMemoryInfo(MemoryInfo memoryInfo);
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 64d6da5..f346fe7 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -276,8 +276,8 @@
}
/**
- * Return the primary external storage directory. This directory may not
- * currently be accessible if it has been mounted by the user on their
+ * Return the primary shared/external storage directory. This directory may
+ * not currently be accessible if it has been mounted by the user on their
* computer, has been removed from the device, or some other problem has
* happened. You can determine its current state with
* {@link #getExternalStorageState()}.
@@ -291,12 +291,15 @@
* filesystem on a computer.</em>
* <p>
* On devices with multiple users (as described by {@link UserManager}),
- * each user has their own isolated external storage. Applications only have
- * access to the external storage for the user they're running as.
+ * each user has their own isolated shared storage. Applications only have
+ * access to the shared storage for the user they're running as.
* <p>
- * In devices with multiple "external" storage directories, this directory
- * represents the "primary" external storage that the user will interact
+ * In devices with multiple shared/external storage directories, this
+ * directory represents the primary storage that the user will interact
* with. Access to secondary storage is available through
+ * {@link Context#getExternalFilesDirs(String)},
+ * {@link Context#getExternalCacheDirs()}, and
+ * {@link Context#getExternalMediaDirs()}.
* <p>
* Applications should not directly use this top-level directory, in order
* to avoid polluting the user's root namespace. Any files that are private
@@ -315,8 +318,9 @@
* <p>
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, if your
* application only needs to store internal data, consider using
- * {@link Context#getExternalFilesDir(String)} or
- * {@link Context#getExternalCacheDir()}, which require no permissions to
+ * {@link Context#getExternalFilesDir(String)},
+ * {@link Context#getExternalCacheDir()}, or
+ * {@link Context#getExternalMediaDirs()}, which require no permissions to
* read or write.
* <p>
* This path may change between platform versions, so applications should
@@ -325,8 +329,7 @@
* Here is an example of typical code to monitor the state of external
* storage:
* <p>
- * {@sample
- * development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* monitor_storage}
*
* @see #getExternalStorageState()
@@ -446,32 +449,32 @@
public static String DIRECTORY_DOCUMENTS = "Documents";
/**
- * Get a top-level public external storage directory for placing files of
- * a particular type. This is where the user will typically place and
- * manage their own files, so you should be careful about what you put here
- * to ensure you don't erase their files or get in the way of their own
+ * Get a top-level shared/external storage directory for placing files of a
+ * particular type. This is where the user will typically place and manage
+ * their own files, so you should be careful about what you put here to
+ * ensure you don't erase their files or get in the way of their own
* organization.
- *
- * <p>On devices with multiple users (as described by {@link UserManager}),
- * each user has their own isolated external storage. Applications only
- * have access to the external storage for the user they're running as.</p>
- *
- * <p>Here is an example of typical code to manipulate a picture on
- * the public external storage:</p>
- *
+ * <p>
+ * On devices with multiple users (as described by {@link UserManager}),
+ * each user has their own isolated shared storage. Applications only have
+ * access to the shared storage for the user they're running as.
+ * </p>
+ * <p>
+ * Here is an example of typical code to manipulate a picture on the public
+ * shared storage:
+ * </p>
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* public_picture}
*
- * @param type The type of storage directory to return. Should be one of
- * {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
- * {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
- * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
- * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or
- * {@link #DIRECTORY_DCIM}. May not be null.
- *
- * @return Returns the File path for the directory. Note that this
- * directory may not yet exist, so you must make sure it exists before
- * using it such as with {@link File#mkdirs File.mkdirs()}.
+ * @param type The type of storage directory to return. Should be one of
+ * {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
+ * {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
+ * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
+ * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or
+ * {@link #DIRECTORY_DCIM}. May not be null.
+ * @return Returns the File path for the directory. Note that this directory
+ * may not yet exist, so you must make sure it exists before using
+ * it such as with {@link File#mkdirs File.mkdirs()}.
*/
public static File getExternalStoragePublicDirectory(String type) {
throwIfUserRequired();
@@ -623,7 +626,7 @@
public static final String MEDIA_EJECTING = "ejecting";
/**
- * Returns the current state of the primary "external" storage device.
+ * Returns the current state of the primary shared/external storage media.
*
* @see #getExternalStorageDirectory()
* @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
@@ -646,8 +649,8 @@
}
/**
- * Returns the current state of the storage device that provides the given
- * path.
+ * Returns the current state of the shared/external storage media at the
+ * given path.
*
* @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
* {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
@@ -665,7 +668,8 @@
}
/**
- * Returns whether the primary "external" storage device is removable.
+ * Returns whether the primary shared/external storage media is physically
+ * removable.
*
* @return true if the storage device can be removed (such as an SD card),
* or false if the storage device is built in and cannot be
@@ -678,8 +682,8 @@
}
/**
- * Returns whether the storage device that provides the given path is
- * removable.
+ * Returns whether the shared/external storage media at the given path is
+ * physically removable.
*
* @return true if the storage device can be removed (such as an SD card),
* or false if the storage device is built in and cannot be
@@ -697,9 +701,15 @@
}
/**
- * Returns whether the primary "external" storage device is emulated. If
- * true, data stored on this device will be stored on a portion of the
- * internal storage system.
+ * Returns whether the primary shared/external storage media is emulated.
+ * <p>
+ * The contents of emulated storage devices are backed by a private user
+ * data partition, which means there is little benefit to apps storing data
+ * here instead of the private directories returned by
+ * {@link Context#getFilesDir()}, etc.
+ * <p>
+ * This returns true when emulated storage is backed by either internal
+ * storage or an adopted storage device.
*
* @see DevicePolicyManager#setStorageEncryption(android.content.ComponentName,
* boolean)
@@ -711,9 +721,16 @@
}
/**
- * Returns whether the storage device that provides the given path is
- * emulated. If true, data stored on this device will be stored on a portion
- * of the internal storage system.
+ * Returns whether the shared/external storage media at the given path is
+ * emulated.
+ * <p>
+ * The contents of emulated storage devices are backed by a private user
+ * data partition, which means there is little benefit to apps storing data
+ * here instead of the private directories returned by
+ * {@link Context#getFilesDir()}, etc.
+ * <p>
+ * This returns true when emulated storage is backed by either internal
+ * storage or an adopted storage device.
*
* @throws IllegalArgumentException if the path is not a valid storage
* device.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 1273772b..5852f5f 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntegerRes;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -42,6 +43,8 @@
import java.util.Map;
import java.util.Set;
+import dalvik.system.VMRuntime;
+
/**
* Container for a message (data and object references) that can
* be sent through an IBinder. A Parcel can contain both flattened data
@@ -193,6 +196,7 @@
* indicating that we're responsible for its lifecycle.
*/
private boolean mOwnsNativeParcelObject;
+ private long mNativeSize;
private RuntimeException mStack;
@@ -244,7 +248,7 @@
private static native int nativeDataAvail(long nativePtr);
private static native int nativeDataPosition(long nativePtr);
private static native int nativeDataCapacity(long nativePtr);
- private static native void nativeSetDataSize(long nativePtr, int size);
+ private static native long nativeSetDataSize(long nativePtr, int size);
private static native void nativeSetDataPosition(long nativePtr, int pos);
private static native void nativeSetDataCapacity(long nativePtr, int size);
@@ -259,7 +263,7 @@
private static native void nativeWriteDouble(long nativePtr, double val);
private static native void nativeWriteString(long nativePtr, String val);
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
- private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
+ private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
private static native byte[] nativeCreateByteArray(long nativePtr);
private static native byte[] nativeReadBlob(long nativePtr);
@@ -272,13 +276,13 @@
private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
private static native long nativeCreate();
- private static native void nativeFreeBuffer(long nativePtr);
+ private static native long nativeFreeBuffer(long nativePtr);
private static native void nativeDestroy(long nativePtr);
private static native byte[] nativeMarshall(long nativePtr);
- private static native void nativeUnmarshall(
+ private static native long nativeUnmarshall(
long nativePtr, byte[] data, int offset, int length);
- private static native void nativeAppendFrom(
+ private static native long nativeAppendFrom(
long thisNativePtr, long otherNativePtr, int offset, int length);
private static native boolean nativeHasFileDescriptors(long nativePtr);
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
@@ -390,7 +394,7 @@
* @param size The new number of bytes in the Parcel.
*/
public final void setDataSize(int size) {
- nativeSetDataSize(mNativePtr, size);
+ updateNativeSize(nativeSetDataSize(mNativePtr, size));
}
/**
@@ -442,11 +446,11 @@
* Set the bytes in data to be the raw bytes of this Parcel.
*/
public final void unmarshall(byte[] data, int offset, int length) {
- nativeUnmarshall(mNativePtr, data, offset, length);
+ updateNativeSize(nativeUnmarshall(mNativePtr, data, offset, length));
}
public final void appendFrom(Parcel parcel, int offset, int length) {
- nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
+ updateNativeSize(nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length));
}
/**
@@ -599,7 +603,24 @@
* if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p>
*/
public final void writeFileDescriptor(FileDescriptor val) {
- nativeWriteFileDescriptor(mNativePtr, val);
+ updateNativeSize(nativeWriteFileDescriptor(mNativePtr, val));
+ }
+
+ private void updateNativeSize(long newNativeSize) {
+ if (mOwnsNativeParcelObject) {
+ if (newNativeSize > Integer.MAX_VALUE) {
+ newNativeSize = Integer.MAX_VALUE;
+ }
+ if (newNativeSize != mNativeSize) {
+ int delta = (int) (newNativeSize - mNativeSize);
+ if (delta > 0) {
+ VMRuntime.getRuntime().registerNativeAllocation(delta);
+ } else {
+ VMRuntime.getRuntime().registerNativeFree(-delta);
+ }
+ mNativeSize = newNativeSize;
+ }
+ }
}
/**
@@ -2545,7 +2566,7 @@
private void freeBuffer() {
if (mOwnsNativeParcelObject) {
- nativeFreeBuffer(mNativePtr);
+ updateNativeSize(nativeFreeBuffer(mNativePtr));
}
}
@@ -2553,6 +2574,7 @@
if (mNativePtr != 0) {
if (mOwnsNativeParcelObject) {
nativeDestroy(mNativePtr);
+ updateNativeSize(0);
}
mNativePtr = 0;
}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 979c828..2445bc2 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -383,6 +383,7 @@
final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
+ filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
mContext.registerReceiver(this, filter);
} else {
mContext.unregisterReceiver(this);
@@ -395,13 +396,7 @@
if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
- final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
- : (streamType == mStreamType);
- if (mSeekBar != null && streamMatch && streamValue != -1) {
- final boolean muted = mAudioManager.isStreamMute(mStreamType)
- || streamValue == 0;
- mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
- }
+ updateVolumeSlider(streamType, streamValue);
} else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
if (mNotificationOrRing) {
mRingerMode = mAudioManager.getRingerModeInternal();
@@ -409,10 +404,24 @@
if (mAffectedByRingerMode) {
updateSlider();
}
+ } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ int streamVolume = mAudioManager.getStreamVolume(streamType);
+ updateVolumeSlider(streamType, streamVolume);
} else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
mZenMode = mNotificationManager.getZenMode();
updateSlider();
}
}
+
+ private void updateVolumeSlider(int streamType, int streamValue) {
+ final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
+ : (streamType == mStreamType);
+ if (mSeekBar != null && streamMatch && streamValue != -1) {
+ final boolean muted = mAudioManager.isStreamMute(mStreamType)
+ || streamValue == 0;
+ mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
+ }
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 33f9476..746e110 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1434,25 +1434,6 @@
}
/**
- * An app can use this method to check if it is currently allowed to change the network
- * state. In order to be allowed to do so, an app must first declare either the
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} or
- * {@link android.Manifest.permission#WRITE_SETTINGS} permission in its manifest. If it
- * is currently disallowed, it can prompt the user to grant it this capability through a
- * management UI by sending an Intent with action
- * {@link android.provider.Settings#ACTION_MANAGE_WRITE_SETTINGS}.
- *
- * @param context A context
- * @return true if the calling app can change the state of network, false otherwise.
- * @hide
- */
- public static boolean canChangeNetworkState(Context context) {
- int uid = Binder.getCallingUid();
- return Settings.isCallingPackageAllowedToChangeNetworkState(context, uid, Settings
- .getPackageNameForUid(context, uid), false);
- }
-
- /**
* System settings, containing miscellaneous system preferences. This
* table holds simple name/value pairs. There are convenience
* functions for accessing individual settings entries.
@@ -8350,7 +8331,7 @@
* write/modify system settings, as the condition differs for pre-M, M+, and
* privileged/preinstalled apps. If the provided uid does not match the
* callingPackage, a negative result will be returned. The caller is expected to have
- * either WRITE_SETTINGS or CHANGE_NETWORK_STATE permission declared.
+ * the WRITE_SETTINGS permission declared.
*
* Note: if the check is successful, the operation of this app will be updated to the
* current time.
@@ -8366,31 +8347,22 @@
/**
* Performs a strict and comprehensive check of whether a calling package is allowed to
* change the state of network, as the condition differs for pre-M, M+, and
- * privileged/preinstalled apps. If the provided uid does not match the
- * callingPackage, a negative result will be returned. The caller is expected to have
- * either of CHANGE_NETWORK_STATE or WRITE_SETTINGS permission declared.
- * @hide
- */
- public static boolean isCallingPackageAllowedToChangeNetworkState(Context context, int uid,
- String callingPackage, boolean throwException) {
- return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
- callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
- PM_CHANGE_NETWORK_STATE, false);
- }
-
- /**
- * Performs a strict and comprehensive check of whether a calling package is allowed to
- * change the state of network, as the condition differs for pre-M, M+, and
- * privileged/preinstalled apps. If the provided uid does not match the
- * callingPackage, a negative result will be returned. The caller is expected to have
- * either CHANGE_NETWORK_STATE or WRITE_SETTINGS permission declared.
+ * privileged/preinstalled apps. The caller is expected to have either the
+ * CHANGE_NETWORK_STATE or the WRITE_SETTINGS permission declared. Either of these
+ * permissions allow changing network state; WRITE_SETTINGS is a runtime permission and
+ * can be revoked, but (except in M, excluding M MRs), CHANGE_NETWORK_STATE is a normal
+ * permission and cannot be revoked. See http://b/23597341
*
- * Note: if the check is successful, the operation of this app will be updated to the
- * current time.
+ * Note: if the check succeeds because the application holds WRITE_SETTINGS, the operation
+ * of this app will be updated to the current time.
* @hide
*/
public static boolean checkAndNoteChangeNetworkStateOperation(Context context, int uid,
String callingPackage, boolean throwException) {
+ if (context.checkCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
PM_CHANGE_NETWORK_STATE, true);
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index db19f7a..8763496 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -41,6 +41,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
@@ -68,6 +69,7 @@
public static final int[] MINUTE_BUCKETS = generateMinuteBuckets();
private static final int SECONDS_MS = 1000;
private static final int MINUTES_MS = 60 * SECONDS_MS;
+ private static final int DAY_MINUTES = 24 * 60;
private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
private static final boolean DEFAULT_ALLOW_CALLS = true;
@@ -652,40 +654,68 @@
boolean shortVersion) {
final long now = System.currentTimeMillis();
final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
- return toTimeCondition(context, now + millis, minutesFromNow, now, userHandle,
- shortVersion);
+ return toTimeCondition(context, now + millis, minutesFromNow, userHandle, shortVersion);
}
- public static Condition toTimeCondition(Context context, long time, int minutes, long now,
+ public static Condition toTimeCondition(Context context, long time, int minutes,
int userHandle, boolean shortVersion) {
- final int num, summaryResId, line1ResId;
+ final int num;
+ String summary, line1, line2;
+ final CharSequence formattedTime = getFormattedTime(context, time, userHandle);
+ final Resources res = context.getResources();
if (minutes < 60) {
// display as minutes
num = minutes;
- summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short
+ int summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short
: R.plurals.zen_mode_duration_minutes_summary;
- line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short
+ summary = res.getQuantityString(summaryResId, num, num, formattedTime);
+ int line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short
: R.plurals.zen_mode_duration_minutes;
- } else {
+ line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
+ line2 = res.getString(R.string.zen_mode_until, formattedTime);
+ } else if (minutes < DAY_MINUTES) {
// display as hours
num = Math.round(minutes / 60f);
- summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short
+ int summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short
: R.plurals.zen_mode_duration_hours_summary;
- line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short
+ summary = res.getQuantityString(summaryResId, num, num, formattedTime);
+ int line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short
: R.plurals.zen_mode_duration_hours;
+ line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
+ line2 = res.getString(R.string.zen_mode_until, formattedTime);
+ } else {
+ // display as day/time
+ summary = line1 = line2 = res.getString(R.string.zen_mode_until, formattedTime);
}
- final String skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma";
- final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- final CharSequence formattedTime = DateFormat.format(pattern, time);
- final Resources res = context.getResources();
- final String summary = res.getQuantityString(summaryResId, num, num, formattedTime);
- final String line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
- final String line2 = res.getString(R.string.zen_mode_until, formattedTime);
final Uri id = toCountdownConditionId(time);
return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE,
Condition.FLAG_RELEVANT_NOW);
}
+ public static Condition toNextAlarmCondition(Context context, long now, long alarm,
+ int userHandle) {
+ final CharSequence formattedTime = getFormattedTime(context, alarm, userHandle);
+ final Resources res = context.getResources();
+ final String line1 = res.getString(R.string.zen_mode_alarm, formattedTime);
+ final Uri id = toCountdownConditionId(alarm);
+ return new Condition(id, "", line1, "", 0, Condition.STATE_TRUE,
+ Condition.FLAG_RELEVANT_NOW);
+ }
+
+ private static CharSequence getFormattedTime(Context context, long time, int userHandle) {
+ String skeleton = "EEE " + (DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma");
+ GregorianCalendar now = new GregorianCalendar();
+ GregorianCalendar endTime = new GregorianCalendar();
+ endTime.setTimeInMillis(time);
+ if (now.get(Calendar.YEAR) == endTime.get(Calendar.YEAR)
+ && now.get(Calendar.MONTH) == endTime.get(Calendar.MONTH)
+ && now.get(Calendar.DATE) == endTime.get(Calendar.DATE)) {
+ skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma";
+ }
+ final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return DateFormat.format(pattern, time);
+ }
+
// ==== Built-in system conditions ====
public static final String SYSTEM_AUTHORITY = "android";
@@ -883,11 +913,6 @@
return UUID.randomUUID().toString().replace("-", "");
}
- public static String getConditionLine1(Context context, ZenModeConfig config,
- int userHandle, boolean shortVersion) {
- return getConditionLine(context, config, userHandle, true /*useLine1*/, shortVersion);
- }
-
public static String getConditionSummary(Context context, ZenModeConfig config,
int userHandle, boolean shortVersion) {
return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion);
@@ -906,8 +931,8 @@
if (time > 0) {
final long now = System.currentTimeMillis();
final long span = time - now;
- c = toTimeCondition(context,
- time, Math.round(span / (float) MINUTES_MS), now, userHandle, shortVersion);
+ c = toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS),
+ userHandle, shortVersion);
}
final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
return TextUtils.isEmpty(rt) ? "" : rt;
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 10a994a..f2b6041 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -16,15 +16,17 @@
package android.text;
-import com.android.internal.annotations.GuardedBy;
-
import android.annotation.Nullable;
import android.util.Log;
-import libcore.io.IoUtils;
+import com.android.internal.annotations.GuardedBy;
import java.io.File;
import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Locale;
@@ -45,19 +47,29 @@
@GuardedBy("sLock")
final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
- final static Hyphenator sEmptyHyphenator = new Hyphenator(StaticLayout.nLoadHyphenator(""));
+ final static Hyphenator sEmptyHyphenator =
+ new Hyphenator(StaticLayout.nLoadHyphenator(null, 0), null);
final private long mNativePtr;
- private Hyphenator(long nativePtr) {
+ // We retain a reference to the buffer to keep the memory mapping valid
+ @SuppressWarnings("unused")
+ final private ByteBuffer mBuffer;
+
+ private Hyphenator(long nativePtr, ByteBuffer b) {
mNativePtr = nativePtr;
+ mBuffer = b;
}
- public static long get(@Nullable Locale locale) {
+ public long getNativePtr() {
+ return mNativePtr;
+ }
+
+ public static Hyphenator get(@Nullable Locale locale) {
synchronized (sLock) {
Hyphenator result = sMap.get(locale);
if (result != null) {
- return result.mNativePtr;
+ return result;
}
// TODO: Convert this a proper locale-fallback system
@@ -67,7 +79,7 @@
result = sMap.get(languageOnlyLocale);
if (result != null) {
sMap.put(locale, result);
- return result.mNativePtr;
+ return result;
}
// Fall back to script-only, if available
@@ -80,22 +92,28 @@
result = sMap.get(scriptOnlyLocale);
if (result != null) {
sMap.put(locale, result);
- return result.mNativePtr;
+ return result;
}
}
sMap.put(locale, sEmptyHyphenator); // To remember we found nothing.
}
- return sEmptyHyphenator.mNativePtr;
+ return sEmptyHyphenator;
}
private static Hyphenator loadHyphenator(String languageTag) {
- String patternFilename = "hyph-"+languageTag.toLowerCase(Locale.US)+".pat.txt";
+ String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb";
File patternFile = new File(getSystemHyphenatorLocation(), patternFilename);
try {
- String patternData = IoUtils.readFileAsString(patternFile.getAbsolutePath());
- long nativePtr = StaticLayout.nLoadHyphenator(patternData);
- return new Hyphenator(nativePtr);
+ RandomAccessFile f = new RandomAccessFile(patternFile, "r");
+ try {
+ FileChannel fc = f.getChannel();
+ MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
+ long nativePtr = StaticLayout.nLoadHyphenator(buf, 0);
+ return new Hyphenator(nativePtr, buf);
+ } finally {
+ f.close();
+ }
} catch (IOException e) {
Log.e(TAG, "error loading hyphenation " + patternFile, e);
return null;
@@ -148,7 +166,7 @@
sMap.put(null, null);
// TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data
- String[] availableLanguages = {"en-US", "eu", "hu", "hy", "nb", "nn", "sa", "und-Ethi"};
+ String[] availableLanguages = {"en-US", "eu", "hu", "hy", "nb", "nn", "und-Ethi"};
for (int i = 0; i < availableLanguages.length; i++) {
String languageTag = availableLanguages[i];
Hyphenator h = loadHyphenator(languageTag);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3b0def2..fdc6cb1 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -29,6 +29,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Locale;
@@ -341,7 +342,8 @@
private void setLocale(Locale locale) {
if (!locale.equals(mLocale)) {
- nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
+ nSetLocale(mNativePtr, locale.toLanguageTag(),
+ Hyphenator.get(locale).getNativePtr());
mLocale = locale;
}
}
@@ -625,7 +627,9 @@
chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
- if (chooseHt.length != 0) {
+ if (chooseHt.length == 0) {
+ chooseHt = null; // So that out() would not assume it has any contents
+ } else {
if (chooseHtv == null ||
chooseHtv.length < chooseHt.length) {
chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
@@ -808,7 +812,7 @@
v = out(source, here, endPos,
fmAscent, fmDescent, fmTop, fmBottom,
- v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
+ v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
chs, widths, paraStart, ellipsize, ellipsizedWidth,
lineWidths[breakIndex], paint, moreChars);
@@ -1243,7 +1247,7 @@
private static native void nFreeBuilder(long nativePtr);
private static native void nFinishBuilder(long nativePtr);
- /* package */ static native long nLoadHyphenator(String patternData);
+ /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset);
private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index c82587b..6d1d893 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -432,23 +432,24 @@
return anim;
}
} else {
- int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
- int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
- int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
- int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
+ sceneRoot.getLocationInWindow(tempLocation);
+ int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
+ int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
+ int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
+ int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
// TODO: also handle size changes: check bounds and animate size changes
if (startX != endX || startY != endY) {
- sceneRoot.getLocationInWindow(tempLocation);
- Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
- Bitmap.Config.ARGB_8888);
+ final int width = view.getWidth();
+ final int height = view.getHeight();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
final BitmapDrawable drawable = new BitmapDrawable(bitmap);
+ drawable.setBounds(startX, startY, startX + width, startY + height);
final float transitionAlpha = view.getTransitionAlpha();
view.setTransitionAlpha(0);
sceneRoot.getOverlay().add(drawable);
- Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0],
- startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]);
+ Path topLeftPath = getPathMotion().getPath(startX, startY, endX, endY);
PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index da3a99b..d85ea65 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2355,7 +2355,13 @@
* 1 PFLAG3_APPLYING_INSETS
* 1 PFLAG3_FITTING_SYSTEM_WINDOWS
* 1 PFLAG3_NESTED_SCROLLING_ENABLED
- * 1 PFLAG3_ASSIST_BLOCKED
+ * 1 PFLAG3_SCROLL_INDICATOR_TOP
+ * 1 PFLAG3_SCROLL_INDICATOR_BOTTOM
+ * 1 PFLAG3_SCROLL_INDICATOR_LEFT
+ * 1 PFLAG3_SCROLL_INDICATOR_RIGHT
+ * 1 PFLAG3_SCROLL_INDICATOR_START
+ * 1 PFLAG3_SCROLL_INDICATOR_END
+ * 1 PFLAG3_ASSIST_BLOCKED
* |-------|-------|-------|-------|
*/
@@ -2549,7 +2555,7 @@
* <p>Indicates that we are allowing {@link ViewStructure} to traverse
* into this view.<p>
*/
- static final int PFLAG3_ASSIST_BLOCKED = 0x100;
+ static final int PFLAG3_ASSIST_BLOCKED = 0x4000;
/**
* Always allow a user to over-scroll this view, provided it is a
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 5b042c6..13c1937 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -16,12 +16,6 @@
package android.widget;
-import java.text.BreakIterator;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-
import android.R;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -112,6 +106,12 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
+import java.text.BreakIterator;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
/**
* Helper class used by TextView to handle editable text views.
@@ -1004,8 +1004,7 @@
}
if (!handled && mTextActionMode != null) {
- // TODO: Fix dragging in extracted mode.
- if (touchPositionIsInSelection() && !mTextView.isInExtractedMode()) {
+ if (touchPositionIsInSelection()) {
// Start a drag
final int start = mTextView.getSelectionStart();
final int end = mTextView.getSelectionEnd();
@@ -4254,10 +4253,14 @@
positionAtCursorOffset(offset, false);
}
+ /**
+ * @param offset Cursor offset. Must be in [-1, length].
+ * @param parentScrolled If the parent has been scrolled or not.
+ */
@Override
protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
super.positionAtCursorOffset(offset, parentScrolled);
- mInWord = !getWordIteratorWithText().isBoundary(offset);
+ mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
}
@Override
@@ -4490,10 +4493,14 @@
positionAtCursorOffset(offset, false);
}
+ /**
+ * @param offset Cursor offset. Must be in [-1, length].
+ * @param parentScrolled If the parent has been scrolled or not.
+ */
@Override
protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
super.positionAtCursorOffset(offset, parentScrolled);
- mInWord = !getWordIteratorWithText().isBoundary(offset);
+ mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
}
@Override
@@ -4860,9 +4867,8 @@
mEndHandle.showAtLocation(endOffset);
// No longer the first dragging motion, reset.
- if (!(mTextView.isInExtractedMode())) {
- startSelectionActionMode();
- }
+ startSelectionActionMode();
+
mDragAcceleratorActive = false;
mStartOffset = -1;
mSwitchedLines = false;
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index ddbaa9d..cc6ab65 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -234,6 +234,8 @@
if (w != mDrawableWidth || h != mDrawableHeight) {
mDrawableWidth = w;
mDrawableHeight = h;
+ // updates the matrix, which is dependent on the bounds
+ configureBounds();
}
}
/* we invalidate the whole view in this case because it's very
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 4dfa7db..abcd614 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -704,7 +704,7 @@
}
final int heightMode;
- if (params.width == LayoutParams.MATCH_PARENT) {
+ if (params.height == LayoutParams.MATCH_PARENT) {
heightMode = MeasureSpec.EXACTLY;
} else {
heightMode = MeasureSpec.AT_MOST;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a1462c4..0c4b60b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
import android.R;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
@@ -115,14 +117,14 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewParent;
-import android.view.ViewStructure;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
import android.view.ViewHierarchyEncoder;
+import android.view.ViewParent;
+import android.view.ViewRootImpl;
+import android.view.ViewStructure;
+import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -149,8 +151,6 @@
import java.util.ArrayList;
import java.util.Locale;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
/**
* Displays text to the user and optionally allows them to edit it. A TextView
* is a complete text editor, however the basic class is configured to not
@@ -1474,7 +1474,12 @@
}
}
}
+ } else if (mText instanceof Spannable) {
+ // Reset the selection.
+ stopTextActionMode();
+ Selection.setSelection((Spannable) mText, getSelectionStart(), getSelectionEnd());
}
+
if (mEditor.hasSelectionController()) {
mEditor.startSelectionActionMode();
}
@@ -5243,14 +5248,6 @@
mEditor.mCreatedWithASelection = false;
}
- // Phone specific code (there is no ExtractEditText on tablets).
- // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
- // not be set. Do the test here instead.
- if (isInExtractedMode() && hasSelection() && mEditor != null
- && mEditor.mTextActionMode == null && isShown() && hasWindowFocus()) {
- mEditor.startSelectionActionMode();
- }
-
unregisterForPreDraw();
return true;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2e0bf06..7699673 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,6 +16,8 @@
package com.android.internal.app;
+import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
@@ -29,6 +31,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.database.DataSetObserver;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -46,13 +49,18 @@
import android.service.chooser.IChooserTargetResult;
import android.service.chooser.IChooserTargetService;
import android.text.TextUtils;
+import android.util.FloatProperty;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ListView;
@@ -80,6 +88,7 @@
private Intent mReferrerFillInIntent;
private ChooserListAdapter mChooserListAdapter;
+ private ChooserRowAdapter mChooserRowAdapter;
private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
@@ -253,7 +262,9 @@
boolean alwaysUseOption) {
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
mChooserListAdapter = (ChooserListAdapter) adapter;
- adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
+ mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
+ mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
+ adapterView.setAdapter(mChooserRowAdapter);
if (listView != null) {
listView.setItemsCanFocus(true);
}
@@ -362,6 +373,11 @@
int targetsToQuery = 0;
for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
+ if (adapter.getScore(dri) == 0) {
+ // A score of 0 means the app hasn't been used in some time;
+ // don't query it as it's not likely to be relevant.
+ continue;
+ }
final ActivityInfo ai = dri.getResolveInfo().activityInfo;
final Bundle md = ai.metaData;
final String serviceName = md != null ? convertServiceName(ai.packageName,
@@ -909,7 +925,63 @@
@Override
public int compare(ChooserTarget lhs, ChooserTarget rhs) {
// Descending order
- return (int) Math.signum(lhs.getScore() - rhs.getScore());
+ return (int) Math.signum(rhs.getScore() - lhs.getScore());
+ }
+ }
+
+ static class RowScale {
+ private static final int DURATION = 400;
+
+ float mScale;
+ ChooserRowAdapter mAdapter;
+ private final ObjectAnimator mAnimator;
+
+ public static final FloatProperty<RowScale> PROPERTY =
+ new FloatProperty<RowScale>("scale") {
+ @Override
+ public void setValue(RowScale object, float value) {
+ object.mScale = value;
+ object.mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public Float get(RowScale object) {
+ return object.mScale;
+ }
+ };
+
+ public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) {
+ mAdapter = adapter;
+ mScale = from;
+ if (from == to) {
+ mAnimator = null;
+ return;
+ }
+
+ mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
+ }
+
+ public RowScale setInterpolator(Interpolator interpolator) {
+ if (mAnimator != null) {
+ mAnimator.setInterpolator(interpolator);
+ }
+ return this;
+ }
+
+ public float get() {
+ return mScale;
+ }
+
+ public void startAnimation() {
+ if (mAnimator != null) {
+ mAnimator.start();
+ }
+ }
+
+ public void cancelAnimation() {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
}
}
@@ -917,15 +989,51 @@
private ChooserListAdapter mChooserListAdapter;
private final LayoutInflater mLayoutInflater;
private final int mColumnCount = 4;
+ private RowScale[] mServiceTargetScale;
+ private final Interpolator mInterpolator;
public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
mChooserListAdapter = wrappedAdapter;
mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
+ mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this,
+ android.R.interpolator.decelerate_quint);
+
wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
+ final int rcount = getServiceTargetRowCount();
+ if (mServiceTargetScale == null
+ || mServiceTargetScale.length != rcount) {
+ RowScale[] old = mServiceTargetScale;
+ int oldRCount = old != null ? old.length : 0;
+ mServiceTargetScale = new RowScale[rcount];
+ if (old != null && rcount > 0) {
+ System.arraycopy(old, 0, mServiceTargetScale, 0,
+ Math.min(old.length, rcount));
+ }
+
+ for (int i = rcount; i < oldRCount; i++) {
+ old[i].cancelAnimation();
+ }
+
+ for (int i = oldRCount; i < rcount; i++) {
+ final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
+ .setInterpolator(mInterpolator);
+ mServiceTargetScale[i] = rs;
+ }
+
+ // Start the animations in a separate loop.
+ // The process of starting animations will result in
+ // binding views to set up initial values, and we must
+ // have ALL of the new RowScale objects created above before
+ // we get started.
+ for (int i = oldRCount; i < rcount; i++) {
+ mServiceTargetScale[i].startAnimation();
+ }
+ }
+
notifyDataSetChanged();
}
@@ -933,19 +1041,43 @@
public void onInvalidated() {
super.onInvalidated();
notifyDataSetInvalidated();
+ if (mServiceTargetScale != null) {
+ for (RowScale rs : mServiceTargetScale) {
+ rs.cancelAnimation();
+ }
+ }
}
});
}
+ private float getRowScale(int rowPosition) {
+ final int start = getCallerTargetRowCount();
+ final int end = start + getServiceTargetRowCount();
+ if (rowPosition >= start && rowPosition < end) {
+ return mServiceTargetScale[rowPosition - start].get();
+ }
+ return 1.f;
+ }
+
@Override
public int getCount() {
return (int) (
- Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
- + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
+ getCallerTargetRowCount()
+ + getServiceTargetRowCount()
+ Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
);
}
+ public int getCallerTargetRowCount() {
+ return (int) Math.ceil(
+ (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
+ }
+
+ public int getServiceTargetRowCount() {
+ return (int) Math.ceil(
+ (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
+ }
+
@Override
public Object getItem(int position) {
// We have nothing useful to return here.
@@ -959,33 +1091,69 @@
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- final View[] holder;
+ final RowViewHolder holder;
if (convertView == null) {
holder = createViewHolder(parent);
} else {
- holder = (View[]) convertView.getTag();
+ holder = (RowViewHolder) convertView.getTag();
}
bindViewHolder(position, holder);
- // We keep the actual list item view as the last item in the holder array
- return holder[mColumnCount];
+ return holder.row;
}
- View[] createViewHolder(ViewGroup parent) {
- final View[] holder = new View[mColumnCount + 1];
-
+ RowViewHolder createViewHolder(ViewGroup parent) {
final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
parent, false);
+ final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
for (int i = 0; i < mColumnCount; i++) {
- holder[i] = mChooserListAdapter.createView(row);
- row.addView(holder[i]);
+ final View v = mChooserListAdapter.createView(row);
+ final int column = i;
+ v.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startSelected(holder.itemIndices[column], false, true);
+ }
+ });
+ v.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ showAppDetails(
+ mChooserListAdapter.resolveInfoForPosition(
+ holder.itemIndices[column], true));
+ return true;
+ }
+ });
+ row.addView(v);
+ holder.cells[i] = v;
+
+ // Force height to be a given so we don't have visual disruption during scaling.
+ LayoutParams lp = v.getLayoutParams();
+ v.measure(spec, spec);
+ if (lp == null) {
+ lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
+ row.setLayoutParams(lp);
+ } else {
+ lp.height = v.getMeasuredHeight();
+ }
+ }
+
+ // Pre-measure so we can scale later.
+ holder.measure();
+ LayoutParams lp = row.getLayoutParams();
+ if (lp == null) {
+ lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
+ row.setLayoutParams(lp);
+ } else {
+ lp.height = holder.measuredRowHeight;
}
row.setTag(holder);
- holder[mColumnCount] = row;
return holder;
}
- void bindViewHolder(int rowPosition, View[] holder) {
+ void bindViewHolder(int rowPosition, RowViewHolder holder) {
final int start = getFirstRowPosition(rowPosition);
final int startType = mChooserListAdapter.getPositionTargetType(start);
@@ -994,34 +1162,26 @@
end--;
}
- final ViewGroup row = (ViewGroup) holder[mColumnCount];
-
if (startType == ChooserListAdapter.TARGET_SERVICE) {
- row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
+ holder.row.setBackgroundColor(
+ getColor(R.color.chooser_service_row_background_color));
} else {
- row.setBackground(null);
+ holder.row.setBackgroundColor(Color.TRANSPARENT);
+ }
+
+ final int oldHeight = holder.row.getLayoutParams().height;
+ holder.row.getLayoutParams().height = Math.max(1,
+ (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
+ if (holder.row.getLayoutParams().height != oldHeight) {
+ holder.row.requestLayout();
}
for (int i = 0; i < mColumnCount; i++) {
- final View v = holder[i];
+ final View v = holder.cells[i];
if (start + i <= end) {
v.setVisibility(View.VISIBLE);
- final int itemIndex = start + i;
- mChooserListAdapter.bindView(itemIndex, v);
- v.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- startSelected(itemIndex, false, true);
- }
- });
- v.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- showAppDetails(
- mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
- return true;
- }
- });
+ holder.itemIndices[i] = start + i;
+ mChooserListAdapter.bindView(holder.itemIndices[i], v);
} else {
v.setVisibility(View.GONE);
}
@@ -1048,6 +1208,25 @@
}
}
+ static class RowViewHolder {
+ final View[] cells;
+ final ViewGroup row;
+ int measuredRowHeight;
+ int[] itemIndices;
+
+ public RowViewHolder(ViewGroup row, int cellCount) {
+ this.row = row;
+ this.cells = new View[cellCount];
+ this.itemIndices = new int[cellCount];
+ }
+
+ public void measure() {
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ row.measure(spec, spec);
+ measuredRowHeight = row.getMeasuredHeight();
+ }
+ }
+
static class ChooserTargetServiceConnection implements ServiceConnection {
private final DisplayResolveInfo mOriginalTarget;
private ComponentName mConnectedComponent;
@@ -1199,4 +1378,44 @@
mSelectedTarget = null;
}
}
+
+ class OffsetDataSetObserver extends DataSetObserver {
+ private final AbsListView mListView;
+ private int mCachedViewType = -1;
+ private View mCachedView;
+
+ public OffsetDataSetObserver(AbsListView listView) {
+ mListView = listView;
+ }
+
+ @Override
+ public void onChanged() {
+ if (mResolverDrawerLayout == null) {
+ return;
+ }
+
+ final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
+ int offset = 0;
+ for (int i = 0; i < chooserTargetRows; i++) {
+ final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
+ final int vt = mChooserRowAdapter.getItemViewType(pos);
+ if (vt != mCachedViewType) {
+ mCachedView = null;
+ }
+ final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
+ int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
+
+ offset += (int) (height * mChooserRowAdapter.getRowScale(pos));
+
+ if (vt >= 0) {
+ mCachedViewType = vt;
+ mCachedView = v;
+ } else {
+ mCachedViewType = -1;
+ }
+ }
+
+ mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 1710489..4ba678c 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -26,6 +26,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
+import android.view.View.OnAttachStateChangeListener;
import android.widget.AbsListView;
import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
@@ -103,6 +104,8 @@
private ResolverComparator mResolverComparator;
private PickTargetOptionRequest mPickOptionRequest;
+ protected ResolverDrawerLayout mResolverDrawerLayout;
+
private boolean mRegistered;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override public void onSomePackagesChanged() {
@@ -253,6 +256,7 @@
if (isVoiceInteraction()) {
rdl.setCollapsed(false);
}
+ mResolverDrawerLayout = rdl;
}
if (title == null) {
@@ -328,6 +332,18 @@
if (isVoiceInteraction()) {
onSetupVoiceInteraction();
}
+
+ getWindow().getDecorView().addOnAttachStateChangeListener(
+ new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().setDrawDuringWindowsAnimating(true);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
}
/**
@@ -1570,7 +1586,10 @@
private void onBindView(View view, TargetInfo info) {
final ViewHolder holder = (ViewHolder) view.getTag();
- holder.text.setText(info.getDisplayLabel());
+ final CharSequence label = info.getDisplayLabel();
+ if (!TextUtils.equals(holder.text.getText(), label)) {
+ holder.text.setText(info.getDisplayLabel());
+ }
if (showsExtendedInfo(info)) {
holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(info.getExtendedInfo());
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index a7bdbe0..8e8d352 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3010,16 +3010,16 @@
int vis = show ? VISIBLE : INVISIBLE;
visibilityChanged = state.targetVisibility != vis;
state.targetVisibility = vis;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (lp.height != resolvedHeight || lp.width != resolvedWidth
+ || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
+ lp.height = resolvedHeight;
+ lp.width = resolvedWidth;
+ lp.gravity = resolvedGravity;
+ lp.rightMargin = rightMargin;
+ view.setLayoutParams(lp);
+ }
if (show) {
- LayoutParams lp = (LayoutParams) view.getLayoutParams();
- if (lp.height != resolvedHeight || lp.width != resolvedWidth
- || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
- lp.height = resolvedHeight;
- lp.width = resolvedWidth;
- lp.gravity = resolvedGravity;
- lp.rightMargin = rightMargin;
- view.setLayoutParams(lp);
- }
view.setBackgroundColor(color);
}
}
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index 44df0ce..b44baa2 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -35,7 +35,7 @@
public class FloatingActionMode extends ActionMode {
private static final int MAX_HIDE_DURATION = 3000;
- private static final int MOVING_HIDE_DELAY = 300;
+ private static final int MOVING_HIDE_DELAY = 50;
private final Context mContext;
private final ActionMode.Callback2 mCallback;
@@ -181,7 +181,6 @@
// Content rect is moving.
mOriginatingView.removeCallbacks(mMovingOff);
mFloatingToolbarVisibilityHelper.setMoving(true);
- mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY);
mFloatingToolbar.setContentRect(mContentRectOnScreen);
@@ -189,9 +188,9 @@
}
} else {
mFloatingToolbarVisibilityHelper.setOutOfBounds(true);
- mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
mContentRectOnScreen.setEmpty();
}
+ mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
mPreviousContentRectOnScreen.set(mContentRectOnScreen);
}
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index ca6fe61..2a25db6 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -1488,10 +1488,9 @@
private static AnimatorSet createEnterAnimation(View view) {
AnimatorSet animation = new AnimatorSet();
animation.playTogether(
- ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(200),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150),
// Make sure that view.x is always fixed throughout the duration of this animation.
ObjectAnimator.ofFloat(view, View.X, view.getX(), view.getX()));
- animation.setStartDelay(50);
return animation;
}
@@ -1506,7 +1505,7 @@
View view, int startDelay, Animator.AnimatorListener listener) {
AnimatorSet animation = new AnimatorSet();
animation.playTogether(
- ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(200));
+ ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
animation.setStartDelay(startDelay);
animation.addListener(listener);
return animation;
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 7679624..c4347f8 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -69,6 +69,12 @@
private int mCollapsibleHeight;
private int mUncollapsibleHeight;
+ /**
+ * The height in pixels of reserved space added to the top of the collapsed UI;
+ * e.g. chooser targets
+ */
+ private int mCollapsibleHeightReserved;
+
private int mTopOffset;
private boolean mIsDragging;
@@ -153,12 +159,62 @@
}
}
+ public void setCollapsibleHeightReserved(int heightPixels) {
+ final int oldReserved = mCollapsibleHeightReserved;
+ mCollapsibleHeightReserved = heightPixels;
+
+ final int dReserved = mCollapsibleHeightReserved - oldReserved;
+ if (dReserved != 0 && mIsDragging) {
+ mLastTouchY -= dReserved;
+ }
+
+ final int oldCollapsibleHeight = mCollapsibleHeight;
+ mCollapsibleHeight = Math.max(mCollapsibleHeight, getMaxCollapsedHeight());
+
+ if (updateCollapseOffset(oldCollapsibleHeight, !isDragging())) {
+ return;
+ }
+
+ invalidate();
+ }
+
private boolean isMoving() {
return mIsDragging || !mScroller.isFinished();
}
+ private boolean isDragging() {
+ return mIsDragging || getNestedScrollAxes() == SCROLL_AXIS_VERTICAL;
+ }
+
+ private boolean updateCollapseOffset(int oldCollapsibleHeight, boolean remainClosed) {
+ if (oldCollapsibleHeight == mCollapsibleHeight) {
+ return false;
+ }
+
+ if (isLaidOut()) {
+ final boolean isCollapsedOld = mCollapseOffset != 0;
+ if (remainClosed && (oldCollapsibleHeight < mCollapsibleHeight
+ && mCollapseOffset == oldCollapsibleHeight)) {
+ // Stay closed even at the new height.
+ mCollapseOffset = mCollapsibleHeight;
+ } else {
+ mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
+ }
+ final boolean isCollapsedNew = mCollapseOffset != 0;
+ if (isCollapsedOld != isCollapsedNew) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ } else {
+ // Start out collapsed at first unless we restored state for otherwise
+ mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
+ }
+ return true;
+ }
+
private int getMaxCollapsedHeight() {
- return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight;
+ return (isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight)
+ + mCollapsibleHeightReserved;
}
public void setOnDismissedListener(OnDismissedListener listener) {
@@ -676,7 +732,7 @@
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.alwaysShow && child.getVisibility() != GONE) {
measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
- heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
+ heightUsed += getHeightUsed(child);
}
}
@@ -688,7 +744,7 @@
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.alwaysShow && child.getVisibility() != GONE) {
measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
- heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
+ heightUsed += getHeightUsed(child);
}
}
@@ -697,30 +753,43 @@
heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
mUncollapsibleHeight = heightUsed - mCollapsibleHeight;
- if (isLaidOut()) {
- final boolean isCollapsedOld = mCollapseOffset != 0;
- if (oldCollapsibleHeight < mCollapsibleHeight
- && mCollapseOffset == oldCollapsibleHeight) {
- // Stay closed even at the new height.
- mCollapseOffset = mCollapsibleHeight;
- } else {
- mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
- }
- final boolean isCollapsedNew = mCollapseOffset != 0;
- if (isCollapsedOld != isCollapsedNew) {
- notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
- }
- } else {
- // Start out collapsed at first unless we restored state for otherwise
- mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
- }
+ updateCollapseOffset(oldCollapsibleHeight, !isDragging());
mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
setMeasuredDimension(sourceWidth, heightSize);
}
+ private int getHeightUsed(View child) {
+ // This method exists because we're taking a fast path at measuring ListViews that
+ // lets us get away with not doing the more expensive wrap_content measurement which
+ // imposes double child view measurement costs. If we're looking at a ListView, we can
+ // check against the lowest child view plus padding and margin instead of the actual
+ // measured height of the ListView. This lets the ListView hang off the edge when
+ // all of the content would fit on-screen.
+
+ int heightUsed = child.getMeasuredHeight();
+ if (child instanceof AbsListView) {
+ final AbsListView lv = (AbsListView) child;
+ final int lvPaddingBottom = lv.getPaddingBottom();
+
+ int lowest = 0;
+ for (int i = 0, N = lv.getChildCount(); i < N; i++) {
+ final int bottom = lv.getChildAt(i).getBottom() + lvPaddingBottom;
+ if (bottom > lowest) {
+ lowest = bottom;
+ }
+ }
+
+ if (lowest < heightUsed) {
+ heightUsed = lowest;
+ }
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ return lp.topMargin + heightUsed + lp.bottomMargin;
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = getWidth();
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 738a62a..cb0abb6 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -1660,10 +1660,11 @@
uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) {
+ // GainMap rectangle is relative to the active area origin.
err = builder.addGainMapsForMetadata(lsmWidth,
lsmHeight,
- ymin,
- xmin,
+ 0,
+ 0,
height,
width,
opcodeCfaLayout,
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index a14afa0..41aa9ca 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -114,7 +114,7 @@
return parcel ? parcel->dataCapacity() : 0;
}
-static void android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jlong nativePtr, jint size)
+static jlong android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jlong nativePtr, jint size)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -122,7 +122,9 @@
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
+ return parcel->getOpenAshmemSize();
}
+ return 0;
}
static void android_os_Parcel_setDataPosition(JNIEnv* env, jclass clazz, jlong nativePtr, jint pos)
@@ -304,7 +306,7 @@
}
}
-static void android_os_Parcel_writeFileDescriptor(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
+static jlong android_os_Parcel_writeFileDescriptor(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -313,7 +315,9 @@
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
+ return parcel->getOpenAshmemSize();
}
+ return 0;
}
static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -546,12 +550,14 @@
return reinterpret_cast<jlong>(parcel);
}
-static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jlong android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
parcel->freeData();
+ return parcel->getOpenAshmemSize();
}
+ return 0;
}
static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -589,12 +595,12 @@
return ret;
}
-static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
- jbyteArray data, jint offset, jint length)
+static jlong android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jbyteArray data, jint offset, jint length)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel == NULL || length < 0) {
- return;
+ return 0;
}
jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
@@ -608,24 +614,26 @@
env->ReleasePrimitiveArrayCritical(data, array, 0);
}
+ return parcel->getOpenAshmemSize();
}
-static void android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr,
- jlong otherNativePtr, jint offset, jint length)
+static jlong android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr,
+ jlong otherNativePtr, jint offset, jint length)
{
Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr);
if (thisParcel == NULL) {
- return;
+ return 0;
}
Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr);
if (otherParcel == NULL) {
- return;
+ return thisParcel->getOpenAshmemSize();
}
status_t err = thisParcel->appendFrom(otherParcel, offset, length);
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
+ return thisParcel->getOpenAshmemSize();
}
static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -718,7 +726,7 @@
{"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
{"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition},
{"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity},
- {"nativeSetDataSize", "(JI)V", (void*)android_os_Parcel_setDataSize},
+ {"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize},
{"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition},
{"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity},
@@ -733,7 +741,7 @@
{"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble},
{"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
{"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
- {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},
+ {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
@@ -751,12 +759,12 @@
{"clearFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
{"nativeCreate", "()J", (void*)android_os_Parcel_create},
- {"nativeFreeBuffer", "(J)V", (void*)android_os_Parcel_freeBuffer},
+ {"nativeFreeBuffer", "(J)J", (void*)android_os_Parcel_freeBuffer},
{"nativeDestroy", "(J)V", (void*)android_os_Parcel_destroy},
{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
- {"nativeUnmarshall", "(J[BII)V", (void*)android_os_Parcel_unmarshall},
- {"nativeAppendFrom", "(JJII)V", (void*)android_os_Parcel_appendFrom},
+ {"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall},
+ {"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom},
{"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index a151e00..83f76ea 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -117,9 +117,17 @@
b->finish();
}
-static jlong nLoadHyphenator(JNIEnv* env, jclass, jstring patternData) {
- ScopedStringChars str(env, patternData);
- Hyphenator* hyphenator = Hyphenator::load(str.get(), str.size());
+static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset) {
+ const uint8_t* bytebuf = nullptr;
+ if (buffer != nullptr) {
+ void* rawbuf = env->GetDirectBufferAddress(buffer);
+ if (rawbuf != nullptr) {
+ bytebuf = reinterpret_cast<const uint8_t*>(rawbuf) + offset;
+ } else {
+ ALOGE("failed to get direct buffer address");
+ }
+ }
+ Hyphenator* hyphenator = Hyphenator::loadBinary(bytebuf);
return reinterpret_cast<jlong>(hyphenator);
}
@@ -177,7 +185,7 @@
{"nNewBuilder", "()J", (void*) nNewBuilder},
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
{"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
- {"nLoadHyphenator", "(Ljava/lang/String;)J", (void*) nLoadHyphenator},
+ {"nLoadHyphenator", "(Ljava/nio/ByteBuffer;I)J", (void*) nLoadHyphenator},
{"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale},
{"nSetupParagraph", "(J[CIFIF[IIII)V", (void*) nSetupParagraph},
{"nSetIndents", "(J[I)V", (void*) nSetIndents},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0614d2c..1f6b6d3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1707,12 +1707,12 @@
android:protectionLevel="signature|privileged" />
<!-- Allows applications to change network connectivity state.
- <p>Protection level: signature
+ <p>Protection level: normal
-->
<permission android:name="android.permission.CHANGE_NETWORK_STATE"
android:description="@string/permdesc_changeNetworkState"
android:label="@string/permlab_changeNetworkState"
- android:protectionLevel="signature|preinstalled|appop|pre23" />
+ android:protectionLevel="normal" />
<!-- Allows an application to clear the caches of all installed
applications on the device.
@@ -2067,10 +2067,11 @@
<permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
android:protectionLevel="signature" />
- <!-- Allows an application to monitor changes in tablet mode.
+ <!-- Allows an application to query tablet mode state and monitor changes
+ in it.
<p>Not for use by third-party applications.
@hide -->
- <permission android:name="android.permission.TABLET_MODE_LISTENER"
+ <permission android:name="android.permission.TABLET_MODE"
android:protectionLevel="signature" />
<!-- Allows an application to request installing packages. Apps
diff --git a/core/res/res/drawable/spinner_background_material.xml b/core/res/res/drawable/spinner_background_material.xml
index d37f5b7..c2a2a26 100644
--- a/core/res/res/drawable/spinner_background_material.xml
+++ b/core/res/res/drawable/spinner_background_material.xml
@@ -17,18 +17,18 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingMode="stack"
android:paddingStart="0dp"
- android:paddingEnd="24dp"
+ android:paddingEnd="48dp"
android:paddingLeft="0dp"
android:paddingRight="0dp">
<item
- android:gravity="end|center_vertical"
- android:width="24dp"
- android:height="24dp"
+ android:gravity="end|fill_vertical"
+ android:width="48dp"
android:drawable="@drawable/control_background_40dp_material" />
<item
android:drawable="@drawable/ic_spinner_caret"
android:gravity="end|center_vertical"
android:width="24dp"
- android:height="24dp" />
+ android:height="24dp"
+ android:end="12dp" />
</layer-list>
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index dcdfb6c..41726fb 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -85,7 +85,7 @@
<ListView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:id="@+id/resolver_list"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 1c496f6..0a7ac77 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -70,6 +70,7 @@
android:minLines="2"
android:maxLines="2"
android:gravity="top|center_horizontal"
- android:ellipsize="marquee" />
+ android:ellipsize="marquee"
+ android:visibility="gone" />
</LinearLayout>
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index fb639ca..0f39e42 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -44,14 +44,5 @@
<item>false</item>
</string-array>
- <!-- String containing the apn value for tethering. May be overriden by secure settings
- TETHER_DUN_APN. Value is a comma separated series of strings:
- "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
- Or string format of ApnSettingV3.
- note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
- <string-array translatable="false" name="config_tether_apndata">
- <item>[ApnSettingV3]SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,204,04,,DUN,,,true,0,,,,,,,gid,5A</item>
- </string-array>
-
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true;BAE0000000000000</string>
</resources>
diff --git a/core/res/res/values-mcc302-mnc780/config.xml b/core/res/res/values-mcc302-mnc780/config.xml
index 51abd36..a48f695 100644
--- a/core/res/res/values-mcc302-mnc780/config.xml
+++ b/core/res/res/values-mcc302-mnc780/config.xml
@@ -21,25 +21,6 @@
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
- <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
- <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
- <integer-array translatable="false" name="config_tether_upstream_types">
- <item>1</item>
- <item>4</item>
- <item>7</item>
- <item>9</item>
- </integer-array>
-
- <!-- String containing the apn value for tethering. May be overriden by secure settings
- TETHER_DUN_APN. Value is a comma separated series of strings:
- "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
- Or string format of ApnSettingV3.
- note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
- <string-array translatable="false" name="config_tether_apndata">
- <item>SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,302,780,,DUN</item>
- </string-array>
-
<!-- Don't use roaming icon for considered operators -->
<string-array translatable="false" name="config_operatorConsideredNonRoaming">
<item>302</item>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 1a45b3a..cf08dea 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2095,6 +2095,10 @@
<enum name="hdpi" value="240" />
<!-- An extra high density screen, approximately 320dpi. -->
<enum name="xhdpi" value="320" />
+ <!-- An extra extra high density screen, approximately 480dpi. -->
+ <enum name="xxhdpi" value="480" />
+ <!-- An extra extra extra high density screen, approximately 640dpi. -->
+ <enum name="xxxhdpi" value="640" />
</attr>
</declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0859e5a..392e503 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -938,6 +938,9 @@
<!-- Is the device capable of hot swapping an UICC Card -->
<bool name="config_hotswapCapable">false</bool>
+ <!-- Component name of the ICC hotswap prompt for restart dialog -->
+ <string name="config_iccHotswapPromptForRestartDialogComponent" translateable="false">@null</string>
+
<!-- Enable puk unlockscreen by default.
If unlock screen is disabled, the puk should be unlocked through Emergency Dialer -->
<bool name="config_enable_puk_unlock_screen">true</bool>
@@ -1246,6 +1249,9 @@
<!-- Operating volatage for bluetooth controller. 0 by default-->
<integer translatable="false" name="config_bluetooth_operating_voltage_mv">4</integer>
+ <!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
+ <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
+
<!-- The default data-use polling period. -->
<integer name="config_datause_polling_period_sec">600</integer>
@@ -1282,6 +1288,10 @@
device is data-only. -->
<bool name="config_voice_capable">true</bool>
+ <!-- Flag indicating that an outbound call must have a call capable phone account
+ that has declared it can process the call's handle. -->
+ <bool name="config_requireCallCapableAccountForHandle">false</bool>
+
<!-- Flag indicating if the user is notified when the mobile network access is restricted -->
<bool name="config_user_notification_of_restrictied_mobile_access">true</bool>
@@ -2348,4 +2358,8 @@
<!-- Name of the component to handle network policy notifications. If present,
disables NetworkPolicyManagerService's presentation of data-usage notifications. -->
<string translatable="false" name="config_networkPolicyNotificationComponent"></string>
+
+ <!-- The BT name of the keyboard packaged with the device. If this is defined, SystemUI will
+ automatically try to pair with it when the device exits tablet mode. -->
+ <string translatable="false" name="config_packagedKeyboardName"></string>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d9fa287..8a9b9cf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4036,6 +4036,9 @@
<!-- Zen mode condition - line two: ending time. [CHAR LIMIT=NONE] -->
<string name="zen_mode_until">Until <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
+ <!-- Zen mode condition - line one: Until next alarm. [CHAR LIMIT=NONE] -->
+ <string name="zen_mode_alarm">Until <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g> (next alarm)</string>
+
<!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] -->
<string name="zen_mode_forever">Until you turn this off</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6d8c38f..7754f86 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -274,6 +274,7 @@
<java-symbol type="bool" name="config_ui_enableFadingMarquee" />
<java-symbol type="bool" name="config_use_strict_phone_number_comparation" />
<java-symbol type="bool" name="config_voice_capable" />
+ <java-symbol type="bool" name="config_requireCallCapableAccountForHandle" />
<java-symbol type="bool" name="config_user_notification_of_restrictied_mobile_access" />
<java-symbol type="bool" name="config_wifiDisplaySupportsProtectedBuffers" />
<java-symbol type="bool" name="preferences_prefer_dual_pane" />
@@ -369,6 +370,7 @@
<java-symbol type="integer" name="config_bluetooth_rx_cur_ma" />
<java-symbol type="integer" name="config_bluetooth_tx_cur_ma" />
<java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
+ <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_drawLockTimeoutMillis" />
<java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -2088,6 +2090,7 @@
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="array" name="config_system_condition_providers" />
<java-symbol type="string" name="muted_by" />
+ <java-symbol type="string" name="zen_mode_alarm" />
<java-symbol type="string" name="select_day" />
<java-symbol type="string" name="select_year" />
@@ -2336,4 +2339,7 @@
<java-symbol type="string" name="config_networkPolicyNotificationComponent" />
<java-symbol type="drawable" name="platlogo_m" />
+
+ <java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
+ <java-symbol type="string" name="config_packagedKeyboardName" />
</resources>
diff --git a/data/keyboards/Vendor_18d1_Product_5018.kcm b/data/keyboards/Vendor_18d1_Product_5018.kcm
new file mode 100644
index 0000000..0ca85a2
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_5018.kcm
@@ -0,0 +1,321 @@
+# Copyright (C) 2015 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.
+
+#
+# Key character map for Google Pixel C Keyboard
+#
+
+type FULL
+
+### Basic QWERTY keys ###
+
+key A {
+ label: 'A'
+ base: 'a'
+ shift, capslock: 'A'
+}
+
+key B {
+ label: 'B'
+ base: 'b'
+ shift, capslock: 'B'
+}
+
+key C {
+ label: 'C'
+ base: 'c'
+ shift, capslock: 'C'
+ alt: '\u00e7'
+ shift+alt: '\u00c7'
+}
+
+key D {
+ label: 'D'
+ base: 'd'
+ shift, capslock: 'D'
+}
+
+key E {
+ label: 'E'
+ base: 'e'
+ shift, capslock: 'E'
+ alt: '\u0301'
+}
+
+key F {
+ label: 'F'
+ base: 'f'
+ shift, capslock: 'F'
+}
+
+key G {
+ label: 'G'
+ base: 'g'
+ shift, capslock: 'G'
+}
+
+key H {
+ label: 'H'
+ base: 'h'
+ shift, capslock: 'H'
+}
+
+key I {
+ label: 'I'
+ base: 'i'
+ shift, capslock: 'I'
+ alt: '\u0302'
+}
+
+key J {
+ label: 'J'
+ base: 'j'
+ shift, capslock: 'J'
+}
+
+key K {
+ label: 'K'
+ base: 'k'
+ shift, capslock: 'K'
+}
+
+key L {
+ label: 'L'
+ base: 'l'
+ shift, capslock: 'L'
+}
+
+key M {
+ label: 'M'
+ base: 'm'
+ shift, capslock: 'M'
+}
+
+key N {
+ label: 'N'
+ base: 'n'
+ shift, capslock: 'N'
+ alt: '\u0303'
+}
+
+key O {
+ label: 'O'
+ base: 'o'
+ shift, capslock: 'O'
+ ralt: '['
+ ralt+shift: '{'
+}
+
+key P {
+ label: 'P'
+ base: 'p'
+ shift, capslock: 'P'
+ ralt: ']'
+ ralt+shift: '}'
+}
+
+key Q {
+ label: 'Q'
+ base: 'q'
+ shift, capslock: 'Q'
+}
+
+key R {
+ label: 'R'
+ base: 'r'
+ shift, capslock: 'R'
+}
+
+key S {
+ label: 'S'
+ base: 's'
+ shift, capslock: 'S'
+ alt: '\u00df'
+}
+
+key T {
+ label: 'T'
+ base: 't'
+ shift, capslock: 'T'
+}
+
+key U {
+ label: 'U'
+ base: 'u'
+ shift, capslock: 'U'
+ alt: '\u0308'
+}
+
+key V {
+ label: 'V'
+ base: 'v'
+ shift, capslock: 'V'
+}
+
+key W {
+ label: 'W'
+ base: 'w'
+ shift, capslock: 'W'
+}
+
+key X {
+ label: 'X'
+ base: 'x'
+ shift, capslock: 'X'
+}
+
+key Y {
+ label: 'Y'
+ base: 'y'
+ shift, capslock: 'Y'
+}
+
+key Z {
+ label: 'Z'
+ base: 'z'
+ shift, capslock: 'Z'
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: ')'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+ ralt: replace ESCAPE
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '@'
+ ralt: '`'
+ ralt+shift: '~'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '#'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '^'
+ alt+shift: '\u0302'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '&'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '*'
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: '('
+}
+
+key SPACE {
+ label: ' '
+ base: ' '
+ alt, meta: fallback SEARCH
+ ctrl: fallback LANGUAGE_SWITCH
+}
+
+key ENTER {
+ label: '\n'
+ base: '\n'
+}
+
+key TAB {
+ label: '\t'
+ base: '\t'
+}
+
+key COMMA {
+ label: ','
+ base: ','
+ shift: '<'
+}
+
+key PERIOD {
+ label: '.'
+ base: '.'
+ shift: '>'
+}
+
+key SLASH {
+ label: '/'
+ base: '/'
+ shift: '?'
+}
+
+key MINUS {
+ label: '-'
+ base: '-'
+ shift: '_'
+}
+
+key EQUALS {
+ label: '='
+ base: '='
+ shift: '+'
+ ralt: '\\'
+ ralt+shift: '|'
+}
+
+key SEMICOLON {
+ label: ';'
+ base: ';'
+ shift: ':'
+}
+
+key APOSTROPHE {
+ label: '\''
+ base: '\''
+ shift: '"'
+}
+
+### Non-printing keys ###
+
+key ESCAPE {
+ base: fallback BACK
+ alt, meta: fallback HOME
+ ctrl: fallback MENU
+}
diff --git a/data/keyboards/Vendor_18d1_Product_5018.kl b/data/keyboards/Vendor_18d1_Product_5018.kl
new file mode 100644
index 0000000..e95ccb5
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_5018.kl
@@ -0,0 +1,84 @@
+# Copyright (C) 2015 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.
+
+#
+# Key layout for Google Pixel C Keyboard
+#
+
+# Row 1
+key 2 1
+key 3 2
+key 4 3
+key 5 4
+key 6 5
+key 7 6
+key 8 7
+key 9 8
+key 10 9
+key 11 0
+key 12 MINUS
+key 14 DEL # Backspace
+
+# Row 2
+key 15 TAB
+key 16 Q
+key 17 W
+key 18 E
+key 19 R
+key 20 T
+key 21 Y
+key 22 U
+key 23 I
+key 24 O
+key 25 P
+key 13 EQUALS
+key 28 ENTER
+
+# Row 3
+key 125 META_LEFT # "Search key"
+key 30 A
+key 31 S
+key 32 D
+key 33 F
+key 34 G
+key 35 H
+key 36 J
+key 37 K
+key 38 L
+key 39 SEMICOLON
+key 40 APOSTROPHE
+
+# Row 4
+key 42 SHIFT_LEFT
+key 44 Z
+key 45 X
+key 46 C
+key 47 V
+key 48 B
+key 49 N
+key 50 M
+key 51 COMMA
+key 52 PERIOD
+key 53 SLASH
+key 54 SHIFT_RIGHT
+
+# Row 5
+key 29 CTRL_LEFT
+key 56 ALT_LEFT
+key 57 SPACE
+key 100 ALT_RIGHT
+key 103 DPAD_UP
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+key 108 DPAD_DOWN
diff --git a/docs/html/guide/topics/manifest/compatible-screens-element.jd b/docs/html/guide/topics/manifest/compatible-screens-element.jd
index 3606b15..de921d2 100644
--- a/docs/html/guide/topics/manifest/compatible-screens-element.jd
+++ b/docs/html/guide/topics/manifest/compatible-screens-element.jd
@@ -9,7 +9,7 @@
<pre>
<<a href="#compatible-screens">compatible-screens</a>>
<<a href="#screen">screen</a> android:<a href="#screenSize">screenSize</a>=["small" | "normal" | "large" | "xlarge"]
- android:<a href="#screenDensity">screenDensity</a>=["ldpi" | "mdpi" | "hdpi" | "xhdpi"] />
+ android:<a href="#screenDensity">screenDensity</a>=["ldpi" | "mdpi" | "hdpi" | "xhdpi" | "xxhdpi" | "xxxhdpi"] />
...
</compatible-screens>
</pre>
@@ -94,11 +94,9 @@
<li>{@code mdpi}</li>
<li>{@code hdpi}</li>
<li>{@code xhdpi}</li>
+ <li>{@code xxhdpi}</li>
+ <li>{@code xxxhdpi}</li>
</ul>
- <p class="note"><strong>Note:</strong> This attribute currently does not accept
- {@code xxhdpi} as a valid value, but you can instead specify {@code 480}
- as the value, which is the approximate threshold for xhdpi screens.</p>
-
<p>For information about the different screen densities, see <a
href="{@docRoot}guide/practices/screens_support.html#range">Supporting Multiple Screens</a>.</p>
</dd>
@@ -110,8 +108,8 @@
<dt>example</dt>
<dd>
<p>If your application is compatible with only small and normal screens, regardless
-of screen density, then you must specify eight different {@code <screen>} elements,
-because each screen size has four different density configurations. You must declare each one of
+of screen density, then you must specify twelve different {@code <screen>} elements,
+because each screen size has six different density configurations. You must declare each one of
these; any combination of size and density that you do <em>not</em> specify is considered a screen
configuration with which your application is <em>not</em> compatible. Here's what the manifest
entry looks like if your application is compatible with only small and normal screens:</p>
@@ -125,11 +123,15 @@
<screen android:screenSize="small" android:screenDensity="mdpi" />
<screen android:screenSize="small" android:screenDensity="hdpi" />
<screen android:screenSize="small" android:screenDensity="xhdpi" />
+ <screen android:screenSize="small" android:screenDensity="xxhdpi" />
+ <screen android:screenSize="small" android:screenDensity="xxxhdpi" />
<!-- all normal size screens -->
<screen android:screenSize="normal" android:screenDensity="ldpi" />
<screen android:screenSize="normal" android:screenDensity="mdpi" />
<screen android:screenSize="normal" android:screenDensity="hdpi" />
<screen android:screenSize="normal" android:screenDensity="xhdpi" />
+ <screen android:screenSize="normal" android:screenDensity="xxhdpi" />
+ <screen android:screenSize="normal" android:screenDensity="xxxhdpi" />
</compatible-screens>
<application ... >
...
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index bbd4c30..1c2c940 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -479,11 +479,6 @@
* <td>{} </p></td>
* <td>This method can be called in any state and calling it does not change
* the object state. </p></td></tr>
- * <tr><td>setPlaybackRate</p></td>
- * <td>any </p></td>
- * <td>{} </p></td>
- * <td>This method can be called in any state and calling it does not change
- * the object state. </p></td></tr>
* <tr><td>setPlaybackParams</p></td>
* <td>any </p></td>
* <td>{} </p></td>
@@ -2243,10 +2238,14 @@
final InputStream fIs = is;
final MediaFormat fFormat = format;
- // Ensure all input streams are closed. It is also a handy
- // way to implement timeouts in the future.
- synchronized(mOpenSubtitleSources) {
- mOpenSubtitleSources.add(is);
+ if (is != null) {
+ // Ensure all input streams are closed. It is also a handy
+ // way to implement timeouts in the future.
+ synchronized(mOpenSubtitleSources) {
+ mOpenSubtitleSources.add(is);
+ }
+ } else {
+ Log.w(TAG, "addSubtitleSource called with null InputStream");
}
// process each subtitle in its own thread
diff --git a/media/java/android/media/midi/IBluetoothMidiService.aidl b/media/java/android/media/midi/IBluetoothMidiService.aidl
new file mode 100644
index 0000000..fe5566d
--- /dev/null
+++ b/media/java/android/media/midi/IBluetoothMidiService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.midi;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.IBinder;
+
+/** @hide */
+interface IBluetoothMidiService
+{
+ IBinder addBluetoothDevice(in BluetoothDevice bluetoothDevice);
+}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 7197dc0..266b0d9 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -169,6 +169,13 @@
/**
* Registers a callback to receive notifications when MIDI devices are added and removed.
*
+ * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately
+ * for any devices that have open ports. This allows applications to know which input
+ * ports are already in use and, therefore, unavailable.
+ *
+ * Applications should call {@link #getDevices} before registering the callback
+ * to get a list of devices already added.
+ *
* @param callback a {@link DeviceCallback} for MIDI device notifications
* @param handler The {@link android.os.Handler Handler} that will be used for delivering the
* device notifications. If handler is null, then the thread used for the
@@ -288,7 +295,6 @@
// fetch MidiDeviceInfo from the server
MidiDeviceInfo deviceInfo = server.getDeviceInfo();
device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken);
- sendOpenDeviceResponse(device, listenerF, handlerF);
} catch (RemoteException e) {
Log.e(TAG, "remote exception in getDeviceInfo()");
}
diff --git a/media/packages/BluetoothMidiService/Android.mk b/media/packages/BluetoothMidiService/Android.mk
index 2c9c3c5..0565925 100644
--- a/media/packages/BluetoothMidiService/Android.mk
+++ b/media/packages/BluetoothMidiService/Android.mk
@@ -3,7 +3,8 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,src)
LOCAL_PACKAGE_NAME := BluetoothMidiService
LOCAL_CERTIFICATE := platform
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
index b0b389a..1cfd55d 100644
--- a/media/packages/BluetoothMidiService/AndroidManifest.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -8,7 +8,7 @@
<application
android:label="@string/app_name">
- <service android:name="BluetoothMidiService"
+ <service android:name=".BluetoothMidiService"
android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
<intent-filter>
<action android:name="android.media.midi.BluetoothMidiService" />
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index e6d59e4..444705c 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -147,14 +147,22 @@
// switch to receiving notifications after initial characteristic read
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
+ // Use writeType that requests acknowledgement.
+ // This improves compatibility with various BLE-MIDI devices.
+ int originalWriteType = characteristic.getWriteType();
+ characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
CLIENT_CHARACTERISTIC_CONFIG);
if (descriptor != null) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
- mBluetoothGatt.writeDescriptor(descriptor);
+ boolean result = mBluetoothGatt.writeDescriptor(descriptor);
+ Log.d(TAG, "writeDescriptor returned " + result);
} else {
Log.e(TAG, "No CLIENT_CHARACTERISTIC_CONFIG for device " + mBluetoothDevice);
}
+
+ characteristic.setWriteType(originalWriteType);
}
@Override
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
index fbde2b4..5541f9f 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
@@ -19,6 +19,7 @@
import android.app.Service;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
+import android.media.midi.IBluetoothMidiService;
import android.media.midi.MidiManager;
import android.os.IBinder;
import android.util.Log;
@@ -34,24 +35,31 @@
@Override
public IBinder onBind(Intent intent) {
- if (MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT.equals(intent.getAction())) {
- BluetoothDevice bluetoothDevice = (BluetoothDevice)intent.getParcelableExtra("device");
+ // Return the interface
+ return mBinder;
+ }
+
+
+ private final IBluetoothMidiService.Stub mBinder = new IBluetoothMidiService.Stub() {
+
+ public IBinder addBluetoothDevice(BluetoothDevice bluetoothDevice) {
+ BluetoothMidiDevice device;
if (bluetoothDevice == null) {
- Log.e(TAG, "no BluetoothDevice in onBind intent");
+ Log.e(TAG, "no BluetoothDevice in addBluetoothDevice()");
return null;
}
-
- BluetoothMidiDevice device;
synchronized (mDeviceServerMap) {
device = mDeviceServerMap.get(bluetoothDevice);
if (device == null) {
- device = new BluetoothMidiDevice(this, bluetoothDevice, this);
+ device = new BluetoothMidiDevice(BluetoothMidiService.this,
+ bluetoothDevice, BluetoothMidiService.this);
+ mDeviceServerMap.put(bluetoothDevice, device);
}
}
return device.getBinder();
}
- return null;
- }
+
+ };
void deviceClosed(BluetoothDevice device) {
synchronized (mDeviceServerMap) {
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 382b2d0..ff14f94 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -3,6 +3,7 @@
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
<uses-permission android:name="android.permission.REMOVE_TASKS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".DocumentsApplication"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 506ec5833..f270d9e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -32,6 +32,7 @@
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.DocumentsContract;
@@ -65,6 +66,8 @@
// TODO: Move it to a shared file when more operations are implemented.
public static final int FAILURE_COPY = 1;
+ private PowerManager mPowerManager;
+
private NotificationManager mNotificationManager;
private Notification.Builder mProgressBuilder;
@@ -129,10 +132,14 @@
return;
}
+ final PowerManager.WakeLock wakeLock = mPowerManager
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
final ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
final DocumentStack stack = intent.getParcelableExtra(EXTRA_STACK);
try {
+ wakeLock.acquire();
+
// Acquire content providers.
mSrcClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
srcs.get(0).authority);
@@ -151,6 +158,8 @@
ContentProviderClient.releaseQuietly(mSrcClient);
ContentProviderClient.releaseQuietly(mDstClient);
+ wakeLock.release();
+
// Dismiss the ongoing copy notification when the copy is done.
mNotificationManager.cancel(mJobId, 0);
@@ -179,7 +188,8 @@
@Override
public void onCreate() {
super.onCreate();
- mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ mPowerManager = getSystemService(PowerManager.class);
+ mNotificationManager = getSystemService(NotificationManager.class);
}
/**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index f5908c5..407838a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -36,6 +36,7 @@
import com.android.documentsui.BaseActivity.State;
import com.android.documentsui.model.RootInfo;
+import com.android.internal.annotations.GuardedBy;
import com.google.android.collect.Maps;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.AbstractFuture;
@@ -81,6 +82,7 @@
private final RootsCache mRoots;
private final State mState;
+ @GuardedBy("mTasks")
private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();
private final int mSortOrder = State.SORT_ORDER_LAST_MODIFIED;
@@ -167,6 +169,12 @@
@Override
public DirectoryResult loadInBackground() {
+ synchronized (mTasks) {
+ return loadInBackgroundLocked();
+ }
+ }
+
+ private DirectoryResult loadInBackgroundLocked() {
if (mFirstPassLatch == null) {
// First time through we kick off all the recent tasks, and wait
// around to see if everyone finishes quickly.
@@ -304,8 +312,10 @@
// Ensure the loader is stopped
onStopLoading();
- for (RecentTask task : mTasks.values()) {
- IoUtils.closeQuietly(task);
+ synchronized (mTasks) {
+ for (RecentTask task : mTasks.values()) {
+ IoUtils.closeQuietly(task);
+ }
}
IoUtils.closeQuietly(mResult);
diff --git a/packages/FusedLocation/res/values-pt-rBR/strings.xml b/packages/FusedLocation/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..4f8277a
--- /dev/null
+++ b/packages/FusedLocation/res/values-pt-rBR/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label" msgid="5379477904423203699">"Localização combinada"</string>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 0380e21..f935f31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -19,6 +19,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
import android.content.Context;
import android.os.ParcelUuid;
import android.util.Log;
@@ -106,6 +107,10 @@
return mAdapter.getScanMode();
}
+ public BluetoothLeScanner getBluetoothLeScanner() {
+ return mAdapter.getBluetoothLeScanner();
+ }
+
public int getState() {
return mAdapter.getState();
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 372fa03..80f4d4c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -127,6 +127,9 @@
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
+ <!-- Listen for keyboard attachment / detachment -->
+ <uses-permission android:name="android.permission.TABLET_MODE" />
+
<!-- Self permission for internal broadcasts. -->
<permission android:name="com.android.systemui.permission.SELF"
android:protectionLevel="signature" />
diff --git a/packages/SystemUI/res/values-en/donottranslate.xml b/packages/SystemUI/res/values-en/donottranslate.xml
deleted file mode 100644
index 9f04e1f..0000000
--- a/packages/SystemUI/res/values-en/donottranslate.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2015 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
- -->
-
-<resources>
- <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
- <item type="string" name="keyguard_indication_charging_time_fast_if_translated">@string/keyguard_indication_charging_time_fast</item>
- <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
- <item type="string" name="keyguard_indication_charging_time_slowly_if_translated">@string/keyguard_indication_charging_time_slowly</item>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 527248c..49f7bdb 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -20,6 +20,7 @@
<attr name="keyCode" format="integer" />
<!-- does this button generate longpress / repeat events? -->
<attr name="keyRepeat" format="boolean" />
+ <attr name="android:contentDescription" />
</declare-styleable>
<declare-styleable name="ToggleSlider">
<attr name="text" format="string" />
diff --git a/packages/SystemUI/res/values/donottranslate.xml b/packages/SystemUI/res/values/donottranslate.xml
index 30ff704..351a1fd 100644
--- a/packages/SystemUI/res/values/donottranslate.xml
+++ b/packages/SystemUI/res/values/donottranslate.xml
@@ -20,10 +20,4 @@
<!-- Date format for display: should match the lockscreen in /policy. -->
<string name="system_ui_date_pattern">@*android:string/system_ui_date_pattern</string>
- <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
- <item type="string" name="keyguard_indication_charging_time_fast_if_translated">@string/keyguard_indication_charging_time</item>
-
- <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
- <item type="string" name="keyguard_indication_charging_time_slowly_if_translated">@string/keyguard_indication_charging_time</item>
-
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index dfa85ce..dc9c9fd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1126,4 +1126,14 @@
<!-- Dialog asking if the tuner should really be removed from settings [CHAR LIMIT=NONE]-->
<string name="remove_from_settings_prompt">Remove System UI Tuner from Settings and stop using all of its features?"</string>
+ <!-- Dialog title asking if Bluetooth should be enabled [CHAR LIMIT=NONE] -->
+ <string name="enable_bluetooth_title">Turn on Bluetooth?</string>
+
+ <!-- Dialog message explaining why Bluetooth should be enabled when a packaged keyboard is
+ conncted to the device [CHAR LIMIT=NONE] -->
+ <string name="enable_bluetooth_message">To connect your keyboard with your tablet, you first have to turn on Bluetooth.</string>
+
+ <!-- Bluetooth enablement ok text [CHAR LIMIT=40] -->
+ <string name="enable_bluetooth_confirmation_ok">Turn on</string>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 33bd726..0b066af 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -48,6 +48,7 @@
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
+ com.android.systemui.keyboard.KeyboardUI.class,
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
new file mode 100644
index 0000000..64f3e13f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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.systemui.keyboard;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+public class BluetoothDialog extends SystemUIDialog {
+
+ public BluetoothDialog(Context context) {
+ super(context);
+
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ setShowForAllUsers(true);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
new file mode 100644
index 0000000..96ee397
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2015 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.systemui.keyboard;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.WindowManager;
+
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeChangedListener {
+ private static final String TAG = "KeyboardUI";
+ private static final boolean DEBUG = false;
+
+ // Give BT some time to start after SyUI comes up. This avoids flashing a dialog in the user's
+ // face because BT starts a little bit later in the boot process than SysUI and it takes some
+ // time for us to receive the signal that it's starting.
+ private static final long BLUETOOTH_START_DELAY_MILLIS = 10 * 1000;
+
+ private static final int STATE_NOT_ENABLED = -1;
+ private static final int STATE_UNKNOWN = 0;
+ private static final int STATE_WAITING_FOR_BOOT_COMPLETED = 1;
+ private static final int STATE_WAITING_FOR_TABLET_MODE_EXIT = 2;
+ private static final int STATE_WAITING_FOR_DEVICE_DISCOVERY = 3;
+ private static final int STATE_WAITING_FOR_BLUETOOTH = 4;
+ private static final int STATE_WAITING_FOR_STATE_PAIRED = 5;
+ private static final int STATE_PAIRING = 6;
+ private static final int STATE_PAIRED = 7;
+ private static final int STATE_USER_CANCELLED = 8;
+ private static final int STATE_DEVICE_NOT_FOUND = 9;
+
+ private static final int MSG_INIT = 0;
+ private static final int MSG_ON_BOOT_COMPLETED = 1;
+ private static final int MSG_PROCESS_KEYBOARD_STATE = 2;
+ private static final int MSG_ENABLE_BLUETOOTH = 3;
+ private static final int MSG_ON_BLUETOOTH_STATE_CHANGED = 4;
+ private static final int MSG_ON_DEVICE_BOND_STATE_CHANGED = 5;
+ private static final int MSG_ON_BLUETOOTH_DEVICE_ADDED = 6;
+ private static final int MSG_ON_BLE_SCAN_FAILED = 7;
+ private static final int MSG_SHOW_BLUETOOTH_DIALOG = 8;
+ private static final int MSG_DISMISS_BLUETOOTH_DIALOG = 9;
+
+ private volatile KeyboardHandler mHandler;
+ private volatile KeyboardUIHandler mUIHandler;
+
+ protected volatile Context mContext;
+
+ private boolean mEnabled;
+ private String mKeyboardName;
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ private LocalBluetoothAdapter mLocalBluetoothAdapter;
+ private LocalBluetoothProfileManager mProfileManager;
+ private boolean mBootCompleted;
+ private long mBootCompletedTime;
+
+ private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN;
+ private ScanCallback mScanCallback;
+ private BluetoothDialog mDialog;
+
+ private int mState;
+
+ @Override
+ public void start() {
+ mContext = super.mContext;
+ HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ mHandler = new KeyboardHandler(thread.getLooper());
+ mHandler.sendEmptyMessage(MSG_INIT);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("KeyboardUI:");
+ pw.println(" mEnabled=" + mEnabled);
+ pw.println(" mBootCompleted=" + mEnabled);
+ pw.println(" mBootCompletedTime=" + mBootCompletedTime);
+ pw.println(" mKeyboardName=" + mKeyboardName);
+ pw.println(" mInTabletMode=" + mInTabletMode);
+ pw.println(" mState=" + stateToString(mState));
+ }
+
+ @Override
+ protected void onBootCompleted() {
+ mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED);
+ }
+
+ @Override
+ public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+ if (DEBUG) {
+ Slog.d(TAG, "onTabletModeChanged(" + whenNanos + ", " + inTabletMode + ")");
+ }
+
+ if (inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_ON
+ || !inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_OFF) {
+ mInTabletMode = inTabletMode ?
+ InputManager.SWITCH_STATE_ON : InputManager.SWITCH_STATE_OFF;
+ processKeyboardState();
+ }
+ }
+
+ // Shoud only be called on the handler thread
+ private void init() {
+ Context context = mContext;
+ mKeyboardName =
+ context.getString(com.android.internal.R.string.config_packagedKeyboardName);
+ if (TextUtils.isEmpty(mKeyboardName)) {
+ if (DEBUG) {
+ Slog.d(TAG, "No packaged keyboard name given.");
+ }
+ return;
+ }
+
+ LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(context, null);
+ if (bluetoothManager == null) {
+ if (DEBUG) {
+ Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance");
+ }
+ return;
+ }
+ mEnabled = true;
+ mCachedDeviceManager = bluetoothManager.getCachedDeviceManager();
+ mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter();
+ mProfileManager = bluetoothManager.getProfileManager();
+ bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());
+
+ InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+ im.registerOnTabletModeChangedListener(this, mHandler);
+ mInTabletMode = im.isInTabletMode();
+
+ processKeyboardState();
+ mUIHandler = new KeyboardUIHandler();
+ }
+
+ // Should only be called on the handler thread
+ private void processKeyboardState() {
+ mHandler.removeMessages(MSG_PROCESS_KEYBOARD_STATE);
+
+ if (!mEnabled) {
+ mState = STATE_NOT_ENABLED;
+ return;
+ }
+
+ if (!mBootCompleted) {
+ mState = STATE_WAITING_FOR_BOOT_COMPLETED;
+ return;
+ }
+
+ if (mInTabletMode != InputManager.SWITCH_STATE_OFF) {
+ if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) {
+ stopScanning();
+ }
+ mState = STATE_WAITING_FOR_TABLET_MODE_EXIT;
+ return;
+ }
+
+ final int btState = mLocalBluetoothAdapter.getState();
+ if (btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON
+ && mState == STATE_WAITING_FOR_BLUETOOTH) {
+ // If we're waiting for bluetooth but it has come on in the meantime, or is coming
+ // on, just dismiss the dialog. This frequently happens during device startup.
+ mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG);
+ }
+
+ if (btState == BluetoothAdapter.STATE_TURNING_ON) {
+ mState = STATE_WAITING_FOR_BLUETOOTH;
+ // Wait for bluetooth to fully come on.
+ return;
+ }
+
+ if (btState != BluetoothAdapter.STATE_ON) {
+ mState = STATE_WAITING_FOR_BLUETOOTH;
+ showBluetoothDialog();
+ return;
+ }
+
+ CachedBluetoothDevice device = getPairedKeyboard();
+ if (mState == STATE_WAITING_FOR_TABLET_MODE_EXIT || mState == STATE_WAITING_FOR_BLUETOOTH) {
+ if (device != null) {
+ // If we're just coming out of tablet mode or BT just turned on,
+ // then we want to go ahead and automatically connect to the
+ // keyboard. We want to avoid this in other cases because we might
+ // be spuriously called after the user has manually disconnected
+ // the keyboard, meaning we shouldn't try to automtically connect
+ // it again.
+ mState = STATE_PAIRED;
+ device.connect(false);
+ return;
+ }
+ mCachedDeviceManager.clearNonBondedDevices();
+ }
+
+ device = getDiscoveredKeyboard();
+ if (device != null) {
+ mState = STATE_PAIRING;
+ device.startPairing();
+ } else {
+ mState = STATE_WAITING_FOR_DEVICE_DISCOVERY;
+ startScanning();
+ }
+ }
+
+ // Should only be called on the handler thread
+ public void onBootCompletedInternal() {
+ mBootCompleted = true;
+ mBootCompletedTime = SystemClock.uptimeMillis();
+ if (mState == STATE_WAITING_FOR_BOOT_COMPLETED) {
+ processKeyboardState();
+ }
+ }
+
+ // Should only be called on the handler thread
+ private void showBluetoothDialog() {
+ if (isUserSetupComplete()) {
+ long now = SystemClock.uptimeMillis();
+ long earliestDialogTime = mBootCompletedTime + BLUETOOTH_START_DELAY_MILLIS;
+ if (earliestDialogTime < now) {
+ mUIHandler.sendEmptyMessage(MSG_SHOW_BLUETOOTH_DIALOG);
+ } else {
+ mHandler.sendEmptyMessageAtTime(MSG_PROCESS_KEYBOARD_STATE, earliestDialogTime);
+ }
+ } else {
+ // If we're in setup wizard and the keyboard is docked, just automatically enable BT.
+ mLocalBluetoothAdapter.enable();
+ }
+ }
+
+ private boolean isUserSetupComplete() {
+ ContentResolver resolver = mContext.getContentResolver();
+ return Secure.getIntForUser(
+ resolver, Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+ }
+
+ private CachedBluetoothDevice getPairedKeyboard() {
+ Set<BluetoothDevice> devices = mLocalBluetoothAdapter.getBondedDevices();
+ for (BluetoothDevice d : devices) {
+ if (mKeyboardName.equals(d.getName())) {
+ return getCachedBluetoothDevice(d);
+ }
+ }
+ return null;
+ }
+
+ private CachedBluetoothDevice getDiscoveredKeyboard() {
+ Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+ for (CachedBluetoothDevice d : devices) {
+ if (d.getName().equals(mKeyboardName)) {
+ return d;
+ }
+ }
+ return null;
+ }
+
+
+ private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice d) {
+ CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(d);
+ if (cachedDevice == null) {
+ cachedDevice = mCachedDeviceManager.addDevice(
+ mLocalBluetoothAdapter, mProfileManager, d);
+ }
+ return cachedDevice;
+ }
+
+ private void startScanning() {
+ BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner();
+ ScanFilter filter = (new ScanFilter.Builder()).setDeviceName(mKeyboardName).build();
+ ScanSettings settings = (new ScanSettings.Builder())
+ .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+ .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .setReportDelay(0)
+ .build();
+ mScanCallback = new KeyboardScanCallback();
+ scanner.startScan(Arrays.asList(filter), settings, mScanCallback);
+ }
+
+ private void stopScanning() {
+ if (mScanCallback != null) {
+ mLocalBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
+ mScanCallback = null;
+ }
+ }
+
+ // Should only be called on the handler thread
+ private void onDeviceAddedInternal(CachedBluetoothDevice d) {
+ if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && d.getName().equals(mKeyboardName)) {
+ stopScanning();
+ d.startPairing();
+ mState = STATE_PAIRING;
+ }
+ }
+
+ // Should only be called on the handler thread
+ private void onBluetoothStateChangedInternal(int bluetoothState) {
+ if (bluetoothState == BluetoothAdapter.STATE_ON && mState == STATE_WAITING_FOR_BLUETOOTH) {
+ processKeyboardState();
+ }
+ }
+
+ // Should only be called on the handler thread
+ private void onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState) {
+ if (d.getName().equals(mKeyboardName) && bondState == BluetoothDevice.BOND_BONDED) {
+ // We don't need to manually connect to the device here because it will automatically
+ // try to connect after it has been paired.
+ mState = STATE_PAIRED;
+ }
+ }
+
+ // Should only be called on the handler thread
+ private void onBleScanFailedInternal() {
+ mScanCallback = null;
+ if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) {
+ mState = STATE_DEVICE_NOT_FOUND;
+ }
+ }
+
+ private final class KeyboardUIHandler extends Handler {
+ public KeyboardUIHandler() {
+ super(Looper.getMainLooper(), null, true /*async*/);
+ }
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_SHOW_BLUETOOTH_DIALOG: {
+ DialogInterface.OnClickListener listener = new BluetoothDialogClickListener();
+ mDialog = new BluetoothDialog(mContext);
+ mDialog.setTitle(R.string.enable_bluetooth_title);
+ mDialog.setMessage(R.string.enable_bluetooth_message);
+ mDialog.setPositiveButton(R.string.enable_bluetooth_confirmation_ok, listener);
+ mDialog.setNegativeButton(android.R.string.cancel, listener);
+ mDialog.show();
+ break;
+ }
+ case MSG_DISMISS_BLUETOOTH_DIALOG: {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ private final class KeyboardHandler extends Handler {
+ public KeyboardHandler(Looper looper) {
+ super(looper, null, true /*async*/);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_INIT: {
+ init();
+ break;
+ }
+ case MSG_ON_BOOT_COMPLETED: {
+ onBootCompletedInternal();
+ break;
+ }
+ case MSG_PROCESS_KEYBOARD_STATE: {
+ processKeyboardState();
+ break;
+ }
+ case MSG_ENABLE_BLUETOOTH: {
+ boolean enable = msg.arg1 == 1;
+ if (enable) {
+ mLocalBluetoothAdapter.enable();
+ } else {
+ mState = STATE_USER_CANCELLED;
+ }
+ }
+ case MSG_ON_BLUETOOTH_STATE_CHANGED: {
+ int bluetoothState = msg.arg1;
+ onBluetoothStateChangedInternal(bluetoothState);
+ break;
+ }
+ case MSG_ON_DEVICE_BOND_STATE_CHANGED: {
+ CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj;
+ int bondState = msg.arg1;
+ onDeviceBondStateChangedInternal(d, bondState);
+ break;
+ }
+ case MSG_ON_BLUETOOTH_DEVICE_ADDED: {
+ BluetoothDevice d = (BluetoothDevice)msg.obj;
+ CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d);
+ onDeviceAddedInternal(cachedDevice);
+ break;
+
+ }
+ case MSG_ON_BLE_SCAN_FAILED: {
+ onBleScanFailedInternal();
+ break;
+ }
+ }
+ }
+ }
+
+ private final class BluetoothDialogClickListener implements DialogInterface.OnClickListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int enable = DialogInterface.BUTTON_POSITIVE == which ? 1 : 0;
+ mHandler.obtainMessage(MSG_ENABLE_BLUETOOTH, enable, 0).sendToTarget();
+ mDialog = null;
+ }
+ }
+
+ private final class KeyboardScanCallback extends ScanCallback {
+
+ private boolean isDeviceDiscoverable(ScanResult result) {
+ final ScanRecord scanRecord = result.getScanRecord();
+ final int flags = scanRecord.getAdvertiseFlags();
+ final int BT_DISCOVERABLE_MASK = 0x03;
+
+ return (flags & BT_DISCOVERABLE_MASK) != 0;
+ }
+
+ @Override
+ public void onBatchScanResults(List<ScanResult> results) {
+ if (DEBUG) {
+ Slog.d(TAG, "onBatchScanResults(" + results.size() + ")");
+ }
+
+ BluetoothDevice bestDevice = null;
+ int bestRssi = Integer.MIN_VALUE;
+
+ for (ScanResult result : results) {
+ if (DEBUG) {
+ Slog.d(TAG, "onBatchScanResults: considering " + result);
+ }
+
+ if (isDeviceDiscoverable(result) && result.getRssi() > bestRssi) {
+ bestDevice = result.getDevice();
+ bestRssi = result.getRssi();
+ }
+ }
+
+ if (bestDevice != null) {
+ mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, bestDevice).sendToTarget();
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ if (DEBUG) {
+ Slog.d(TAG, "onScanFailed(" + errorCode + ")");
+ }
+ mHandler.obtainMessage(MSG_ON_BLE_SCAN_FAILED).sendToTarget();
+ }
+
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ if (DEBUG) {
+ Slog.d(TAG, "onScanResult(" + callbackType + ", " + result + ")");
+ }
+
+ if (isDeviceDiscoverable(result)) {
+ mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED,
+ result.getDevice()).sendToTarget();
+ } else if (DEBUG) {
+ Slog.d(TAG, "onScanResult: device " + result.getDevice() +
+ " is not discoverable, ignoring");
+ }
+ }
+ }
+
+ private final class BluetoothCallbackHandler implements BluetoothCallback {
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED,
+ bluetoothState, 0).sendToTarget();
+ }
+
+ @Override
+ public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+ mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED,
+ bondState, 0, cachedDevice).sendToTarget();
+ }
+
+ @Override
+ public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { }
+ @Override
+ public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { }
+ @Override
+ public void onScanningStateChanged(boolean started) { }
+ @Override
+ public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { }
+ }
+
+ private static String stateToString(int state) {
+ switch (state) {
+ case STATE_NOT_ENABLED:
+ return "STATE_NOT_ENABLED";
+ case STATE_WAITING_FOR_BOOT_COMPLETED:
+ return "STATE_WAITING_FOR_BOOT_COMPLETED";
+ case STATE_WAITING_FOR_TABLET_MODE_EXIT:
+ return "STATE_WAITING_FOR_TABLET_MODE_EXIT";
+ case STATE_WAITING_FOR_DEVICE_DISCOVERY:
+ return "STATE_WAITING_FOR_DEVICE_DISCOVERY";
+ case STATE_WAITING_FOR_BLUETOOTH:
+ return "STATE_WAITING_FOR_BLUETOOTH";
+ case STATE_WAITING_FOR_STATE_PAIRED:
+ return "STATE_WAITING_FOR_STATE_PAIRED";
+ case STATE_PAIRING:
+ return "STATE_PAIRING";
+ case STATE_PAIRED:
+ return "STATE_PAIRED";
+ case STATE_USER_CANCELLED:
+ return "STATE_USER_CANCELLED";
+ case STATE_DEVICE_NOT_FOUND:
+ return "STATE_DEVICE_NOT_FOUND";
+ case STATE_UNKNOWN:
+ default:
+ return "STATE_UNKNOWN";
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 803a014..728d558 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -21,6 +21,7 @@
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
import android.net.Uri;
import android.os.Looper;
import android.os.PowerManager;
@@ -36,7 +37,7 @@
* - whenever audio is played, audio focus is requested,
* - whenever audio playback is stopped or the playback completed, audio focus is abandoned.
*/
-public class NotificationPlayer implements OnCompletionListener {
+public class NotificationPlayer implements OnCompletionListener, OnErrorListener {
private static final int PLAY = 1;
private static final int STOP = 2;
private static final boolean mDebug = false;
@@ -112,6 +113,7 @@
// done playing. This class should be modified to use a single thread, on which
// command are issued, and on which it receives the completion callbacks.
player.setOnCompletionListener(NotificationPlayer.this);
+ player.setOnErrorListener(NotificationPlayer.this);
player.start();
if (mPlayer != null) {
mPlayer.release();
@@ -245,6 +247,13 @@
}
}
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Log.e(mTag, "error " + what + " (extra=" + extra + ") playing notification");
+ // error happened, handle it just like a completion
+ onCompletion(mp);
+ return true;
+ }
+
private String mTag;
private CmdThread mThread;
private CreationAndCompletionThread mCompletionThread;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index b330582..e4a37fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Animatable;
@@ -320,6 +321,7 @@
public interface Host {
void startActivityDismissingKeyguard(Intent intent);
+ void startActivityDismissingKeyguard(PendingIntent intent);
void warn(String message, Throwable t);
void collapsePanels();
Looper getLooper();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index 3d0dc7b..c7f2284 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -99,7 +99,7 @@
try {
if (pi != null) {
if (pi.isActivity()) {
- getHost().startActivityDismissingKeyguard(pi.getIntent());
+ getHost().startActivityDismissingKeyguard(pi);
} else {
pi.send();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 2e0b80a..be618e2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -162,6 +162,14 @@
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
break;
}
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int index = ev.getActionIndex();
+ mActivePointerId = ev.getPointerId(index);
+ mLastMotionX = (int) ev.getX(index);
+ mLastMotionY = (int) ev.getY(index);
+ mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+ break;
+ }
case MotionEvent.ACTION_MOVE: {
if (mActivePointerId == INACTIVE_POINTER_ID) break;
@@ -187,6 +195,20 @@
mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
break;
}
+ case MotionEvent.ACTION_POINTER_UP: {
+ int pointerIndex = ev.getActionIndex();
+ int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // Select a new active pointer id and reset the motion state
+ final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ mLastMotionX = (int) ev.getX(newPointerIndex);
+ mLastMotionY = (int) ev.getY(newPointerIndex);
+ mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+ mVelocityTracker.clear();
+ }
+ break;
+ }
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
// Animate the scroll back if we've cancelled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index eecac63..9ff86eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1490,6 +1490,59 @@
return true;
}
+ public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
+ if (!isDeviceProvisioned()) return;
+
+ final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+ mCurrentUserId);
+ dismissKeyguardThenExecute(new OnDismissAction() {
+ public boolean onDismiss() {
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ if (keyguardShowing && !afterKeyguardGone) {
+ ActivityManagerNative.getDefault()
+ .keyguardWaitingForActivityDrawn();
+ }
+
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: " + e);
+
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ overrideActivityPendingAppTransition(keyguardShowing
+ && !afterKeyguardGone);
+ }
+ }
+ }.start();
+
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */, true /* delayed */);
+ visibilityChanged(false);
+
+ return true;
+ }
+ }, afterKeyguardGone);
+ }
+
private final class NotificationClicker implements View.OnClickListener {
public void onClick(final View v) {
if (!(v instanceof ExpandableNotificationRow)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 5f01306..56e9af5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -32,6 +32,7 @@
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.LinearInterpolator;
+import android.widget.Chronometer;
import android.widget.ImageView;
import com.android.systemui.R;
@@ -88,6 +89,7 @@
private NotificationGuts mGuts;
private StatusBarNotification mStatusBarNotification;
private boolean mIsHeadsUp;
+ private boolean mLastChronometerRunning = true;
private View mExpandButton;
private View mExpandButtonDivider;
private ViewStub mExpandButtonStub;
@@ -294,6 +296,7 @@
*/
public void setPinned(boolean pinned) {
mIsPinned = pinned;
+ setChronometerRunning(mLastChronometerRunning);
}
public boolean isPinned() {
@@ -319,6 +322,41 @@
return mJustClicked;
}
+ public void setChronometerRunning(boolean running) {
+ mLastChronometerRunning = running;
+ setChronometerRunning(running, mPrivateLayout);
+ setChronometerRunning(running, mPublicLayout);
+ if (mChildrenContainer != null) {
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getNotificationChildren();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow child = notificationChildren.get(i);
+ child.setChronometerRunning(running);
+ }
+ }
+ }
+
+ private void setChronometerRunning(boolean running, NotificationContentView layout) {
+ if (layout != null) {
+ running = running || isPinned();
+ View contractedChild = layout.getContractedChild();
+ View expandedChild = layout.getExpandedChild();
+ View headsUpChild = layout.getHeadsUpChild();
+ setChronometerRunningForChild(running, contractedChild);
+ setChronometerRunningForChild(running, expandedChild);
+ setChronometerRunningForChild(running, headsUpChild);
+ }
+ }
+
+ private void setChronometerRunningForChild(boolean running, View child) {
+ if (child != null) {
+ View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
+ if (chronometer instanceof Chronometer) {
+ ((Chronometer) chronometer).setStarted(running);
+ }
+ }
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 50d274d..fd84345 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -200,12 +200,12 @@
switch (mChargingSpeed) {
case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST:
chargingId = hasChargingTime
- ? R.string.keyguard_indication_charging_time_fast_if_translated
+ ? R.string.keyguard_indication_charging_time_fast
: R.string.keyguard_plugged_in_charging_fast;
break;
case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY:
chargingId = hasChargingTime
- ? R.string.keyguard_indication_charging_time_slowly_if_translated
+ ? R.string.keyguard_indication_charging_time_slowly
: R.string.keyguard_plugged_in_charging_slowly;
break;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index f7c3c67..cc30882 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -250,6 +250,7 @@
@Override
public void setNoSims(boolean show) {
mNoSimsVisible = show && !mBlockMobile;
+ apply();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
index 9ef320bc..8f689c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.app.PendingIntent;
import android.content.Intent;
/**
@@ -24,6 +25,7 @@
* Keyguard.
*/
public interface ActivityStarter {
+ void startPendingIntentDismissingKeyguard(PendingIntent intent);
void startActivity(Intent intent, boolean dismissShade);
void startActivity(Intent intent, boolean dismissShade, Callback callback);
void preventNextAnimation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 8465101..73361bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3210,6 +3210,15 @@
return !isDeviceProvisioned() || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0;
}
+ public void postStartActivityDismissingKeyguard(final PendingIntent intent) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ startPendingIntentDismissingKeyguard(intent);
+ }
+ });
+ }
+
public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
mHandler.postDelayed(new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 540b9d0..fa9c4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -109,6 +109,14 @@
}
};
+ private Runnable mRemoveCastIconRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW");
+ mService.setIconVisibility(SLOT_CAST, false);
+ }
+ };
+
public PhoneStatusBarPolicy(Context context, CastController cast, HotspotController hotspot,
UserInfoController userInfoController, BluetoothController bluetooth) {
mContext = context;
@@ -328,11 +336,17 @@
}
}
if (DEBUG) Log.v(TAG, "updateCast: isCasting: " + isCasting);
+ mHandler.removeCallbacks(mRemoveCastIconRunnable);
if (isCasting) {
mService.setIcon(SLOT_CAST, R.drawable.stat_sys_cast, 0,
mContext.getString(R.string.accessibility_casting));
+ mService.setIconVisibility(SLOT_CAST, true);
+ } else {
+ // don't turn off the screen-record icon for a few seconds, just to make sure the user
+ // has seen it
+ if (DEBUG) Log.v(TAG, "updateCast: hiding icon in 3 sec...");
+ mHandler.postDelayed(mRemoveCastIconRunnable, 3000);
}
- mService.setIconVisibility(SLOT_CAST, isCasting);
}
private void profileChanged(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 12434ac..e66c63b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -134,6 +135,11 @@
}
@Override
+ public void startActivityDismissingKeyguard(PendingIntent intent) {
+ mStatusBar.postStartActivityDismissingKeyguard(intent);
+ }
+
+ @Override
public void warn(String message, Throwable t) {
// already logged
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index e9c4e49..971978d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -527,8 +527,8 @@
startBatteryActivity();
} else if (v == mAlarmStatus && mNextAlarm != null) {
PendingIntent showIntent = mNextAlarm.getShowIntent();
- if (showIntent != null && showIntent.isActivity()) {
- mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */);
+ if (showIntent != null) {
+ mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index ed9b123..4a95d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -169,7 +169,6 @@
*/
public void showNotification(NotificationData.Entry headsUp) {
if (DEBUG) Log.v(TAG, "showNotification");
- MetricsLogger.count(mContext, "note_peek", 1);
addHeadsUpEntry(headsUp);
updateNotification(headsUp, true);
headsUp.setInterruption();
@@ -246,6 +245,9 @@
return;
}
mHasPinnedNotification = hasPinnedNotification;
+ if (mHasPinnedNotification) {
+ MetricsLogger.count(mContext, "note_peek", 1);
+ }
updateTouchableRegionListener();
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
@@ -580,6 +582,13 @@
@Override
public int compareTo(HeadsUpEntry o) {
+ boolean selfFullscreen = hasFullScreenIntent(entry);
+ boolean otherFullscreen = hasFullScreenIntent(o.entry);
+ if (selfFullscreen && !otherFullscreen) {
+ return -1;
+ } else if (!selfFullscreen && otherFullscreen) {
+ return 1;
+ }
return postTime < o.postTime ? 1
: postTime == o.postTime ? entry.key.compareTo(o.entry.key)
: -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 4c99792..4d268ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -18,12 +18,14 @@
import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -43,6 +45,7 @@
public class KeyButtonView extends ImageView {
+ private int mContentDescriptionRes;
private long mDownTime;
private int mCode;
private int mTouchSlop;
@@ -79,8 +82,14 @@
mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
+ TypedValue value = new TypedValue();
+ if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
+ mContentDescriptionRes = value.resourceId;
+ }
+
a.recycle();
+
setClickable(true);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -88,6 +97,15 @@
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ if (mContentDescriptionRes != 0) {
+ setContentDescription(mContext.getString(mContentDescriptionRes));
+ }
+ }
+
+ @Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (mCode != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index b2df40a..0e91b0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -27,7 +27,6 @@
void removeCallback(Callback callback);
void setZen(int zen, Uri conditionId, String reason);
int getZen();
- void requestConditions(boolean request);
ZenRule getManualRule();
ZenModeConfig getConfig();
long getNextAlarm();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index c07f1a8..96efea1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -121,15 +121,6 @@
}
@Override
- public void requestConditions(boolean request) {
- mRequesting = request;
- mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
- if (!mRequesting) {
- mConditions.clear();
- }
- }
-
- @Override
public ZenRule getManualRule() {
return mConfig == null ? null : mConfig.manualRule;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index ae086d4..36c4531 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1778,6 +1778,7 @@
((ExpandableView) child).setOnHeightChangedListener(this);
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
+ updateChronometerForChild(child);
if (canChildBeDismissed(child)) {
// Make sure the dismissButton is visible and not in the animated state.
// We need to do this to avoid a race where a clearable notification is added after the
@@ -2287,6 +2288,21 @@
mStackScrollAlgorithm.setIsExpanded(isExpanded);
if (changed) {
updateNotificationAnimationStates();
+ updateChronometers();
+ }
+ }
+
+ private void updateChronometers() {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ updateChronometerForChild(getChildAt(i));
+ }
+ }
+
+ private void updateChronometerForChild(View child) {
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ row.setChronometerRunning(mIsExpanded);
}
}
@@ -2309,6 +2325,7 @@
}
mStackScrollAlgorithm.onReset(view);
updateAnimationState(view);
+ updateChronometerForChild(view);
}
private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 7836411..e9f1095 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -104,6 +104,7 @@
private final SpTexts mSpTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
private final KeyguardManager mKeyguard;
+ private final AudioManager mAudioManager;
private final int mExpandButtonAnimationDuration;
private final ZenFooter mZenFooter;
private final LayoutTransition mLayoutTransition;
@@ -135,6 +136,7 @@
mCallback = callback;
mSpTexts = new SpTexts(mContext);
mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mDialog = new CustomDialog(mContext);
@@ -636,7 +638,8 @@
private void updateFooterH() {
if (D.BUG) Log.d(TAG, "updateFooterH");
final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
- final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF;
+ final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
+ && mAudioManager.isStreamAffectedByRingerMode(mActiveStream);
if (wasVisible != visible && !visible) {
prepareForCollapse();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 3c9a7fc..3337714 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -58,6 +58,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Objects;
@@ -76,6 +78,8 @@
private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
private static final int FOREVER_CONDITION_INDEX = 0;
private static final int COUNTDOWN_CONDITION_INDEX = 1;
+ private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
+ private static final int COUNTDOWN_CONDITION_COUNT = 2;
public static final Intent ZEN_SETTINGS
= new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
@@ -86,7 +90,6 @@
private final LayoutInflater mInflater;
private final H mHandler = new H();
private final ZenPrefs mPrefs;
- private final IconPulser mIconPulser;
private final TransitionHelper mTransitionHelper = new TransitionHelper();
private final Uri mForeverId;
private final SpTexts mSpTexts;
@@ -104,9 +107,6 @@
private Callback mCallback;
private ZenModeController mController;
private boolean mCountdownConditionSupported;
- private int mMaxConditions;
- private int mMaxOptionalConditions;
- private int mFirstConditionIndex;
private boolean mRequestingConditions;
private Condition mExitCondition;
private int mBucketIndex = -1;
@@ -125,7 +125,6 @@
mContext = context;
mPrefs = new ZenPrefs();
mInflater = LayoutInflater.from(mContext.getApplicationContext());
- mIconPulser = new IconPulser(mContext);
mForeverId = Condition.newId(mContext).appendPath("forever").build();
mSpTexts = new SpTexts(mContext);
mVoiceCapable = Util.isVoiceCapable(mContext);
@@ -135,7 +134,6 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("ZenModePanel state:");
pw.print(" mCountdownConditionSupported="); pw.println(mCountdownConditionSupported);
- pw.print(" mMaxConditions="); pw.println(mMaxConditions);
pw.print(" mRequestingConditions="); pw.println(mRequestingConditions);
pw.print(" mAttached="); pw.println(mAttached);
pw.print(" mHidden="); pw.println(mHidden);
@@ -286,14 +284,6 @@
if (mRequestingConditions == requesting) return;
if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
mRequestingConditions = requesting;
- if (mController != null) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- mController.requestConditions(requesting);
- }
- });
- }
if (mRequestingConditions) {
mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
if (mTimeCondition != null) {
@@ -304,6 +294,7 @@
MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
}
if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
+
mConditions = null; // reset conditions
handleUpdateConditions();
} else {
@@ -314,13 +305,9 @@
public void init(ZenModeController controller) {
mController = controller;
mCountdownConditionSupported = mController.isCountdownConditionSupported();
- final int countdownDelta = mCountdownConditionSupported ? 1 : 0;
- mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta;
+ final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0;
final int minConditions = 1 /*forever*/ + countdownDelta;
- mMaxConditions = MathUtils.constrain(mContext.getResources()
- .getInteger(R.integer.zen_mode_max_conditions), minConditions, 100);
- mMaxOptionalConditions = mMaxConditions - minConditions;
- for (int i = 0; i < mMaxConditions; i++) {
+ for (int i = 0; i < minConditions; i++) {
mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
}
mSessionZen = getSelectedZen(-1);
@@ -357,28 +344,10 @@
return condition == null ? null : condition.copy();
}
- public static String getExitConditionText(Context context, Condition exitCondition) {
- if (exitCondition == null) {
- return foreverSummary(context);
- } else if (isCountdown(exitCondition)) {
- final Condition condition = parseExistingTimeCondition(context, exitCondition);
- return condition != null ? condition.summary : foreverSummary(context);
- } else {
- return exitCondition.summary;
- }
- }
-
public void setCallback(Callback callback) {
mCallback = callback;
}
- public void showSilentHint() {
- if (DEBUG) Log.d(mTag, "showSilentHint");
- if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
- final View noneButton = mZenButtons.getChildAt(0);
- mIconPulser.start(noneButton);
- }
-
private void handleUpdateManualRule(ZenRule rule) {
final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
handleUpdateZen(zen);
@@ -410,7 +379,7 @@
final ConditionTag tag = getConditionTagAt(i);
if (tag != null) {
if (sameConditionId(tag.condition, mExitCondition)) {
- bind(exitCondition, mZenConditions.getChildAt(i));
+ bind(exitCondition, mZenConditions.getChildAt(i), i);
}
}
}
@@ -495,64 +464,32 @@
final long span = time - now;
if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
return ZenModeConfig.toTimeCondition(context,
- time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser(),
+ time, Math.round(span / (float) MINUTES_MS), ActivityManager.getCurrentUser(),
false /*shortVersion*/);
}
- private void handleUpdateConditions(Condition[] conditions) {
- conditions = trimConditions(conditions);
- if (Arrays.equals(conditions, mConditions)) {
- final int count = mConditions == null ? 0 : mConditions.length;
- if (DEBUG) Log.d(mTag, "handleUpdateConditions unchanged conditionCount=" + count);
- return;
- }
- mConditions = conditions;
- handleUpdateConditions();
- }
-
- private Condition[] trimConditions(Condition[] conditions) {
- if (conditions == null || conditions.length <= mMaxOptionalConditions) {
- // no need to trim
- return conditions;
- }
- // look for current exit condition, ensure it is included if found
- int found = -1;
- for (int i = 0; i < conditions.length; i++) {
- final Condition c = conditions[i];
- if (mSessionExitCondition != null && sameConditionId(mSessionExitCondition, c)) {
- found = i;
- break;
- }
- }
- final Condition[] rt = Arrays.copyOf(conditions, mMaxOptionalConditions);
- if (found >= mMaxOptionalConditions) {
- // found after the first N, promote to the end of the first N
- rt[mMaxOptionalConditions - 1] = conditions[found];
- }
- return rt;
- }
-
private void handleUpdateConditions() {
if (mTransitionHelper.isTransitioning()) {
- mTransitionHelper.pendingUpdateConditions();
return;
}
final int conditionCount = mConditions == null ? 0 : mConditions.length;
if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount);
// forever
- bind(forever(), mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
+ bind(forever(), mZenConditions.getChildAt(FOREVER_CONDITION_INDEX),
+ FOREVER_CONDITION_INDEX);
// countdown
if (mCountdownConditionSupported && mTimeCondition != null) {
- bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX));
+ bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX),
+ COUNTDOWN_CONDITION_INDEX);
}
- // provider conditions
- for (int i = 0; i < conditionCount; i++) {
- bind(mConditions[i], mZenConditions.getChildAt(mFirstConditionIndex + i));
- }
- // hide the rest
- for (int i = mZenConditions.getChildCount() - 1; i > mFirstConditionIndex + conditionCount;
- i--) {
- mZenConditions.getChildAt(i).setVisibility(GONE);
+ // countdown until alarm
+ if (mCountdownConditionSupported) {
+ Condition nextAlarmCondition = getTimeUntilNextAlarmCondition();
+ if (nextAlarmCondition != null) {
+ bind(nextAlarmCondition,
+ mZenConditions.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX),
+ COUNTDOWN_ALARM_CONDITION_INDEX);
+ }
}
// ensure something is selected
if (mExpanded && isShown()) {
@@ -569,6 +506,33 @@
return context.getString(com.android.internal.R.string.zen_mode_forever);
}
+ // Returns a time condition if the next alarm is within the next week.
+ private Condition getTimeUntilNextAlarmCondition() {
+ GregorianCalendar weekRange = new GregorianCalendar();
+ final long now = weekRange.getTimeInMillis();
+ setToMidnight(weekRange);
+ weekRange.roll(Calendar.DATE, 6);
+ final long nextAlarmMs = mController.getNextAlarm();
+ if (nextAlarmMs > 0) {
+ GregorianCalendar nextAlarm = new GregorianCalendar();
+ nextAlarm.setTimeInMillis(nextAlarmMs);
+ setToMidnight(nextAlarm);
+
+ if (weekRange.compareTo(nextAlarm) >= 0) {
+ return ZenModeConfig.toNextAlarmCondition(mContext, now, nextAlarmMs,
+ ActivityManager.getCurrentUser());
+ }
+ }
+ return null;
+ }
+
+ private void setToMidnight(Calendar calendar) {
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ }
+
private ConditionTag getConditionTagAt(int index) {
return (ConditionTag) mZenConditions.getChildAt(index).getTag();
}
@@ -610,7 +574,8 @@
mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
MINUTE_BUCKETS[favoriteIndex], ActivityManager.getCurrentUser());
mBucketIndex = favoriteIndex;
- bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX));
+ bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX),
+ COUNTDOWN_CONDITION_INDEX);
getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
}
}
@@ -623,7 +588,7 @@
return c != null && mForeverId.equals(c.id);
}
- private void bind(final Condition condition, final View row) {
+ private void bind(final Condition condition, final View row, final int rowId) {
if (condition == null) throw new IllegalArgumentException("condition must not be null");
final boolean enabled = condition.state == Condition.STATE_TRUE;
final ConditionTag tag =
@@ -640,8 +605,7 @@
tag.rb.setEnabled(enabled);
final boolean checked = (mSessionExitCondition != null
|| mAttachedZen != Global.ZEN_MODE_OFF)
- && (sameConditionId(mSessionExitCondition, tag.condition)
- || isCountdown(mSessionExitCondition) && isCountdown(tag.condition));
+ && (sameConditionId(mSessionExitCondition, tag.condition));
if (checked != tag.rb.isChecked()) {
if (DEBUG) Log.d(mTag, "bind checked=" + checked + " condition=" + conditionId);
tag.rb.setChecked(checked);
@@ -692,7 +656,7 @@
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- onClickTimeButton(row, tag, false /*down*/);
+ onClickTimeButton(row, tag, false /*down*/, rowId);
}
});
@@ -700,7 +664,7 @@
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- onClickTimeButton(row, tag, true /*up*/);
+ onClickTimeButton(row, tag, true /*up*/, rowId);
}
});
tag.lines.setOnClickListener(new OnClickListener() {
@@ -711,7 +675,7 @@
});
final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
- if (time > 0) {
+ if (rowId != COUNTDOWN_ALARM_CONDITION_INDEX && time > 0) {
button1.setVisibility(VISIBLE);
button2.setVisibility(VISIBLE);
if (mBucketIndex > -1) {
@@ -761,7 +725,7 @@
tag.line1.getText()));
}
- private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
+ private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
MetricsLogger.action(mContext, MetricsLogger.QS_DND_TIME, up);
Condition newCondition = null;
final int N = MINUTE_BUCKETS.length;
@@ -777,7 +741,7 @@
if (up && bucketTime > time || !up && bucketTime < time) {
mBucketIndex = j;
newCondition = ZenModeConfig.toTimeCondition(mContext,
- bucketTime, bucketMinutes, now, ActivityManager.getCurrentUser(),
+ bucketTime, bucketMinutes, ActivityManager.getCurrentUser(),
false /*shortVersion*/);
break;
}
@@ -794,7 +758,7 @@
MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
}
mTimeCondition = newCondition;
- bind(mTimeCondition, row);
+ bind(mTimeCondition, row, rowId);
tag.rb.setChecked(true);
select(mTimeCondition);
announceConditionSelection(tag);
@@ -838,18 +802,12 @@
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
@Override
- public void onConditionsChanged(Condition[] conditions) {
- mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
- }
-
- @Override
public void onManualRuleChanged(ZenRule rule) {
mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
}
};
private final class H extends Handler {
- private static final int UPDATE_CONDITIONS = 1;
private static final int MANUAL_RULE_CHANGED = 2;
private static final int UPDATE_WIDGETS = 3;
@@ -860,7 +818,6 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case UPDATE_CONDITIONS: handleUpdateConditions((Condition[]) msg.obj); break;
case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break;
case UPDATE_WIDGETS: updateWidgets(); break;
}
@@ -1005,26 +962,20 @@
private final ArraySet<View> mTransitioningViews = new ArraySet<View>();
private boolean mTransitioning;
- private boolean mPendingUpdateConditions;
private boolean mPendingUpdateWidgets;
public void clear() {
mTransitioningViews.clear();
- mPendingUpdateConditions = mPendingUpdateWidgets = false;
+ mPendingUpdateWidgets = false;
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" TransitionHelper state:");
- pw.print(" mPendingUpdateConditions="); pw.println(mPendingUpdateConditions);
pw.print(" mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets);
pw.print(" mTransitioning="); pw.println(mTransitioning);
pw.print(" mTransitioningViews="); pw.println(mTransitioningViews);
}
- public void pendingUpdateConditions() {
- mPendingUpdateConditions = true;
- }
-
public void pendingUpdateWidgets() {
mPendingUpdateWidgets = true;
}
@@ -1050,15 +1001,11 @@
@Override
public void run() {
if (DEBUG) Log.d(mTag, "TransitionHelper run"
- + " mPendingUpdateWidgets=" + mPendingUpdateWidgets
- + " mPendingUpdateConditions=" + mPendingUpdateConditions);
+ + " mPendingUpdateWidgets=" + mPendingUpdateWidgets);
if (mPendingUpdateWidgets) {
updateWidgets();
}
- if (mPendingUpdateConditions) {
- handleUpdateConditions();
- }
- mPendingUpdateWidgets = mPendingUpdateConditions = false;
+ mPendingUpdateWidgets = false;
}
private void updateTransitioning() {
@@ -1067,10 +1014,10 @@
mTransitioning = transitioning;
if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning);
if (!mTransitioning) {
- if (mPendingUpdateConditions || mPendingUpdateWidgets) {
+ if (mPendingUpdateWidgets) {
mHandler.post(this);
} else {
- mPendingUpdateConditions = mPendingUpdateWidgets = false;
+ mPendingUpdateWidgets = false;
}
}
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 882899e..08f0952 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -971,8 +971,8 @@
// This is a special alarm that will put the system into idle until it goes off.
// The caller has given the time they want this to happen at, however we need
// to pull that earlier if there are existing alarms that have requested to
- // bring us out of idle.
- if (mNextWakeFromIdle != null) {
+ // bring us out of idle at an earlier time.
+ if (mNextWakeFromIdle != null && a.whenElapsed > mNextWakeFromIdle.whenElapsed) {
a.when = a.whenElapsed = a.maxWhenElapsed = mNextWakeFromIdle.whenElapsed;
}
// Add fuzz to make the alarm go off some time before the actual desired time.
@@ -1256,7 +1256,7 @@
pw.print(" Idling until: ");
if (mPendingIdleUntil != null) {
pw.println(mPendingIdleUntil);
- mPendingIdleUntil.dump(pw, " ", nowRTC, nowELAPSED, sdf);
+ mPendingIdleUntil.dump(pw, " ", nowRTC, nowELAPSED, sdf);
} else {
pw.println("null");
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6190a5a..327fb8a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -946,13 +946,13 @@
uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
}
- if ((uidRules & RULE_REJECT_ALL) != 0
- || (networkCostly && (uidRules & RULE_REJECT_METERED) != 0)) {
+ if (uidRules == RULE_REJECT_ALL) {
return true;
+ } else if ((uidRules == RULE_REJECT_METERED) && networkCostly) {
+ return true;
+ } else {
+ return false;
}
-
- // no restrictive rules; network is visible
- return false;
}
/**
@@ -1447,10 +1447,7 @@
}
private void enforceChangePermission() {
- int uid = Binder.getCallingUid();
- Settings.checkAndNoteChangeNetworkStateOperation(mContext, uid, Settings
- .getPackageNameForUid(mContext, uid), true);
-
+ ConnectivityManager.enforceChangePermission(mContext);
}
private void enforceTetherAccessPermission() {
@@ -3212,6 +3209,11 @@
final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
final VpnProfile profile = VpnProfile.decode(
profileName, mKeyStore.get(Credentials.VPN + profileName));
+ if (profile == null) {
+ Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
+ setLockdownTracker(null);
+ return true;
+ }
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
@@ -3358,7 +3360,7 @@
.setPriority(highPriority ?
Notification.PRIORITY_HIGH :
Notification.PRIORITY_DEFAULT)
- .setDefaults(Notification.DEFAULT_ALL)
+ .setDefaults(highPriority ? Notification.DEFAULT_ALL : 0)
.setOnlyAlertOnce(true)
.build();
@@ -3724,7 +3726,7 @@
synchronized(mRulesLock) {
uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
}
- if ((uidRules & (RULE_REJECT_METERED | RULE_REJECT_ALL)) != 0) {
+ if (uidRules != RULE_ALLOW_ALL) {
// we could silently fail or we can filter the available nets to only give
// them those they have access to. Chose the more useful
networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 5239ede..a057ac9 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -244,6 +244,14 @@
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
int plugged = intent.getIntExtra("plugged", 0);
updateChargingLocked(plugged != 0);
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+ if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ Uri data = intent.getData();
+ String ssp;
+ if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
+ removePowerSaveWhitelistAppInternal(ssp);
+ }
+ }
} else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
synchronized (DeviceIdleController.this) {
stepIdleStateLocked();
@@ -979,6 +987,10 @@
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(ACTION_STEP_IDLE_STATE);
getContext().registerReceiver(mReceiver, filter);
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ getContext().registerReceiver(mReceiver, filter);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
@@ -991,7 +1003,10 @@
public boolean addPowerSaveWhitelistAppInternal(String name) {
synchronized (this) {
try {
- ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name, 0);
+ ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS
+ | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid)) == null) {
reportPowerSaveWhitelistChangedLocked();
updateWhitelistAppIdsLocked();
@@ -1332,7 +1347,6 @@
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
EventLogTags.writeDeviceIdle(mState, "step");
scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);
- cancelSensingAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.checkForAnyMotion();
mNotMoving = false;
@@ -1344,7 +1358,6 @@
mState = STATE_LOCATING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
EventLogTags.writeDeviceIdle(mState, "step");
- cancelSensingAlarmLocked();
scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);
if (mLocationManager != null
&& mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
@@ -1414,6 +1427,7 @@
mState = STATE_ACTIVE;
mInactiveTimeout = timeout;
EventLogTags.writeDeviceIdle(mState, type);
+ cancelSensingAlarmLocked();
becomeInactiveIfAppropriateLocked();
}
}
@@ -1595,7 +1609,6 @@
} catch (IOException e) {
}
}
-
}
private void readConfigFileLocked(XmlPullParser parser) {
@@ -1624,7 +1637,10 @@
String name = parser.getAttributeValue(null, "n");
if (name != null) {
try {
- ApplicationInfo ai = pm.getApplicationInfo(name, 0);
+ ApplicationInfo ai = pm.getApplicationInfo(name,
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS
+ | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
mPowerSaveWhitelistUserApps.put(ai.packageName,
UserHandle.getAppId(ai.uid));
} catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 4e11070..6d07a57 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -40,6 +40,7 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.AlertDialog;
@@ -286,8 +287,19 @@
boolean mSystemReady;
/**
- * Id of the currently selected input method.
+ * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
+ * method. This is to be synchronized with the secure settings keyed with
+ * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
+ *
+ * <p>This can be transiently {@code null} when the system is re-initializing input method
+ * settings, e.g., the system locale is just changed.</p>
+ *
+ * <p>Note that {@link #mCurId} is used to track which IME is being connected to
+ * {@link InputMethodManagerService}.</p>
+ *
+ * @see #mCurId
*/
+ @Nullable
String mCurMethodId;
/**
@@ -317,9 +329,14 @@
EditorInfo mCurAttribute;
/**
- * The input method ID of the input method service that we are currently
+ * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
* connected to or in the process of connecting to.
+ *
+ * <p>This can be {@code null} when no input method is connected.</p>
+ *
+ * @see #mCurMethodId
*/
+ @Nullable
String mCurId;
/**
@@ -967,7 +984,6 @@
|| (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
if (!updateOnlyWhenLocaleChanged) {
hideCurrentInputLocked(0, null);
- mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
if (DEBUG) {
@@ -1523,7 +1539,11 @@
channel.dispose();
}
- void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
+ void unbindCurrentMethodLocked(boolean resetCurrentMethodAndClient, boolean savePosition) {
+ if (resetCurrentMethodAndClient) {
+ mCurMethodId = null;
+ }
+
if (mVisibleBound) {
mContext.unbindService(mVisibleConnection);
mVisibleBound = false;
@@ -1550,9 +1570,8 @@
mCurId = null;
clearCurMethodLocked();
- if (reportToClient && mCurClient != null) {
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
- MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+ if (resetCurrentMethodAndClient) {
+ unbindCurrentClientLocked();
}
}
@@ -1903,13 +1922,11 @@
setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
- mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
- mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
// Here is not the perfect place to reset the switching controller. Ideally
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 4e721da..5e67414 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2415,8 +2415,13 @@
}
try {
- mCryptConnector.execute("cryptfs", "enablecrypto", "inplace", CRYPTO_TYPES[type],
- new SensitiveArg(password));
+ if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
+ mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
+ CRYPTO_TYPES[type]);
+ } else {
+ mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
+ CRYPTO_TYPES[type], new SensitiveArg(password));
+ }
} catch (NativeDaemonConnectorException e) {
// Encryption failed
return e.getCode();
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 433f707..ba9279c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -2023,9 +2023,9 @@
public void setFirewallChainEnabled(int chain, boolean enable) {
enforceSystemUid();
synchronized (mQuotaLock) {
- if (mFirewallChainStates.indexOfKey(chain) >= 0 &&
- mFirewallChainStates.get(chain) == enable) {
- // All is the same, nothing to do.
+ if (mFirewallChainStates.get(chain, false) == enable) {
+ // All is the same, nothing to do. This relies on the fact that netd has child
+ // chains default detached.
return;
}
mFirewallChainStates.put(chain, enable);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b87e109..970f1b5 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1314,6 +1314,15 @@
if (!mRestartingServices.contains(r)) {
return;
}
+ if (!isServiceNeeded(r, false, false)) {
+ // Paranoia: is this service actually needed? In theory a service that is not
+ // needed should never remain on the restart list. In practice... well, there
+ // have been bugs where this happens, and bad things happen because the process
+ // ends up just being cached, so quickly killed, then restarted again and again.
+ // Let's not let that happen.
+ Slog.wtf(TAG, "Restarting service that is not needed: " + r);
+ return;
+ }
try {
bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
} catch (TransactionTooLargeException e) {
@@ -2043,6 +2052,13 @@
mAm.mProcessStats);
realStartServiceLocked(sr, proc, sr.createdFromFg);
didSomething = true;
+ if (!isServiceNeeded(sr, false, false)) {
+ // We were waiting for this service to start, but it is actually no
+ // longer needed. This could happen because bringDownServiceIfNeeded
+ // won't bring down a service that is pending... so now the pending
+ // is done, so let's drop it.
+ bringDownServiceLocked(sr);
+ }
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting service "
@@ -2055,7 +2071,7 @@
// be weird to bring up the process but arbitrarily not let the services
// run at this point just because their restart time hasn't come up.
if (mRestartingServices.size() > 0) {
- ServiceRecord sr = null;
+ ServiceRecord sr;
for (int i=0; i<mRestartingServices.size(); i++) {
sr = mRestartingServices.get(i);
if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1195e83..70c4821 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2468,8 +2468,13 @@
final void doPendingActivityLaunchesLocked(boolean doResume) {
while (!mPendingActivityLaunches.isEmpty()) {
PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
- startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
- doResume && mPendingActivityLaunches.isEmpty(), null, null);
+
+ try {
+ startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
+ doResume && mPendingActivityLaunches.isEmpty(), null, null);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception during pending activity launch pal=" + pal, e);
+ }
}
}
@@ -3920,7 +3925,7 @@
mLockTaskModeTasks.add(task);
if (task.mLockTaskUid == -1) {
- task.mLockTaskUid = task.mCallingUid;
+ task.mLockTaskUid = task.effectiveUid;
}
if (andResume) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 960cbf1..6de8579 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -44,6 +44,7 @@
import android.os.UserHandle;
import android.util.EventLog;
import android.util.Slog;
+import android.util.TimeUtils;
import com.android.server.DeviceIdleController;
import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -1284,6 +1285,7 @@
final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0
|| mPendingBroadcast != null) {
boolean printed = false;
@@ -1301,7 +1303,7 @@
pw.println(" Active broadcasts [" + mQueueName + "]:");
}
pw.println(" Active Broadcast " + mQueueName + " #" + i + ":");
- br.dump(pw, " ");
+ br.dump(pw, " ", sdf);
}
printed = false;
needSep = true;
@@ -1319,7 +1321,7 @@
pw.println(" Active ordered broadcasts [" + mQueueName + "]:");
}
pw.println(" Active Ordered Broadcast " + mQueueName + " #" + i + ":");
- mOrderedBroadcasts.get(i).dump(pw, " ");
+ mOrderedBroadcasts.get(i).dump(pw, " ", sdf);
}
if (dumpPackage == null || (mPendingBroadcast != null
&& dumpPackage.equals(mPendingBroadcast.callerPackage))) {
@@ -1328,7 +1330,7 @@
}
pw.println(" Pending broadcast [" + mQueueName + "]:");
if (mPendingBroadcast != null) {
- mPendingBroadcast.dump(pw, " ");
+ mPendingBroadcast.dump(pw, " ", sdf);
} else {
pw.println(" (null)");
}
@@ -1366,7 +1368,7 @@
if (dumpAll) {
pw.print(" Historical Broadcast " + mQueueName + " #");
pw.print(i); pw.println(":");
- r.dump(pw, " ");
+ r.dump(pw, " ", sdf);
} else {
pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r);
pw.print(" ");
@@ -1400,7 +1402,6 @@
}
// done skipping; dump the remainder of the ring. 'i' is still the ordinal within
// the overall broadcast history.
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
do {
ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
Intent intent = mBroadcastSummaryHistory[ringIndex];
@@ -1422,9 +1423,19 @@
i++;
pw.print(" #"); pw.print(i); pw.print(": ");
pw.println(intent.toShortString(false, true, true, false));
- pw.print(" enq="); pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
- pw.print(" disp="); pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
- pw.print(" fin="); pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
+ pw.print(" ");
+ TimeUtils.formatDuration(mSummaryHistoryDispatchTime[ringIndex]
+ - mSummaryHistoryEnqueueTime[ringIndex], pw);
+ pw.print(" dispatch ");
+ TimeUtils.formatDuration(mSummaryHistoryFinishTime[ringIndex]
+ - mSummaryHistoryDispatchTime[ringIndex], pw);
+ pw.println(" finish");
+ pw.print(" enq=");
+ pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
+ pw.print(" disp=");
+ pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
+ pw.print(" fin=");
+ pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
Bundle bundle = intent.getExtras();
if (bundle != null) {
pw.print(" extras: "); pw.println(bundle.toString());
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 1fbfd9f..b42bcff 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -32,6 +32,7 @@
import android.util.TimeUtils;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -88,7 +89,7 @@
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // info about the receiver that is currently running.
- void dump(PrintWriter pw, String prefix) {
+ void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
final long now = SystemClock.uptimeMillis();
pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId);
@@ -114,13 +115,19 @@
pw.print(prefix); pw.print("options="); pw.println(options.toBundle());
}
pw.print(prefix); pw.print("enqueueClockTime=");
- pw.print(new Date(enqueueClockTime));
+ pw.print(sdf.format(new Date(enqueueClockTime)));
pw.print(" dispatchClockTime=");
- pw.println(new Date(dispatchClockTime));
+ pw.println(sdf.format(new Date(dispatchClockTime)));
pw.print(prefix); pw.print("dispatchTime=");
TimeUtils.formatDuration(dispatchTime, now, pw);
+ pw.print(" (");
+ TimeUtils.formatDuration(dispatchClockTime-enqueueClockTime, pw);
+ pw.print(" since enq)");
if (finishTime != 0) {
pw.print(" finishTime="); TimeUtils.formatDuration(finishTime, now, pw);
+ pw.print(" (");
+ TimeUtils.formatDuration(finishTime-dispatchTime, pw);
+ pw.print(" since disp)");
} else {
pw.print(" receiverTime="); TimeUtils.formatDuration(receiverTime, now, pw);
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 334bc18..b61f90e 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -104,6 +104,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
import java.util.Set;
@@ -3370,7 +3371,7 @@
if (!smaller.containsKey(key)) {
return false;
}
- if (!bigger.get(key).equals(smaller.get(key))) {
+ if (!Objects.equals(bigger.get(key), smaller.get(key))) {
return false;
}
}
@@ -3378,7 +3379,6 @@
}
/**
- * TODO: Get rid of this when we separate sync settings extras from dev specified extras.
* @return true if the provided key is used by the SyncManager in scheduling the sync.
*/
private static boolean isSyncSetting(String key) {
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index bb4dbc3..835ba17 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -74,6 +74,7 @@
// Set to true when the animation context has been fully prepared.
private boolean mPrepared;
+ private boolean mCreatedResources;
private int mMode;
private final DisplayManagerInternal mDisplayManagerInternal;
@@ -169,6 +170,7 @@
}
// Done.
+ mCreatedResources = true;
mPrepared = true;
// Dejanking optimization.
@@ -313,6 +315,34 @@
}
/**
+ * Dismisses the color fade animation resources.
+ *
+ * This function destroys the resources that are created for the color fade
+ * animation but does not clean up the surface.
+ */
+ public void dismissResources() {
+ if (DEBUG) {
+ Slog.d(TAG, "dismissResources");
+ }
+
+ if (mCreatedResources) {
+ attachEglContext();
+ try {
+ destroyScreenshotTexture();
+ destroyGLShaders();
+ destroyGLBuffers();
+ destroyEglSurface();
+ } finally {
+ detachEglContext();
+ }
+ // This is being called with no active context so shouldn't be
+ // needed but is safer to not change for now.
+ GLES20.glFlush();
+ mCreatedResources = false;
+ }
+ }
+
+ /**
* Dismisses the color fade animation surface and cleans up.
*
* To prevent stray photons from leaking out after the color fade has been
@@ -325,17 +355,8 @@
}
if (mPrepared) {
- attachEglContext();
- try {
- destroyScreenshotTexture();
- destroyGLShaders();
- destroyGLBuffers();
- destroyEglSurface();
- } finally {
- detachEglContext();
- }
+ dismissResources();
destroySurface();
- GLES20.glFlush();
mPrepared = false;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 452378f..7b49530 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -837,6 +837,7 @@
if (mPendingScreenOff && target != Display.STATE_OFF) {
setScreenState(Display.STATE_OFF);
mPendingScreenOff = false;
+ mPowerState.dismissColorFadeResources();
}
if (target == Display.STATE_ON) {
@@ -910,6 +911,7 @@
// A black surface is already hiding the contents of the screen.
setScreenState(Display.STATE_OFF);
mPendingScreenOff = false;
+ mPowerState.dismissColorFadeResources();
} else if (performScreenOffTransition
&& mPowerState.prepareColorFade(mContext,
mColorFadeFadesConfig ?
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 2eabd32..9862516 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -187,7 +187,7 @@
}
/**
- * Dismisses the electron beam surface.
+ * Dismisses the color fade surface.
*/
public void dismissColorFade() {
mColorFade.dismiss();
@@ -195,6 +195,13 @@
mColorFadeReady = true;
}
+ /**
+ * Dismisses the color fade resources.
+ */
+ public void dismissColorFadeResources() {
+ mColorFade.dismissResources();
+ }
+
/**
* Sets the level of the electron beam steering current.
*
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5a13672..0205a20 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -792,8 +792,17 @@
}
@Override // Binder call
+ public int isInTabletMode() {
+ if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE,
+ "isInTabletMode()")) {
+ throw new SecurityException("Requires TABLET_MODE permission");
+ }
+ return getSwitchState(-1, InputDevice.SOURCE_ANY, SW_TABLET_MODE);
+ }
+
+ @Override // Binder call
public void registerTabletModeChangedListener(ITabletModeChangedListener listener) {
- if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE_LISTENER,
+ if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE,
"registerTabletModeChangedListener()")) {
throw new SecurityException("Requires TABLET_MODE_LISTENER permission");
}
@@ -1488,7 +1497,7 @@
switchMask);
}
- if ((switchMask & SW_TABLET_MODE) != 0) {
+ if ((switchMask & SW_TABLET_MODE_BIT) != 0) {
SomeArgs args = SomeArgs.obtain();
args.argi1 = (int) (whenNanos & 0xFFFFFFFF);
args.argi2 = (int) (whenNanos >> 32);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index ecda36a..3c50102 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -73,7 +73,7 @@
*/
public class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener {
- static final boolean DEBUG = false;
+ public static final boolean DEBUG = false;
/** The number of concurrent jobs we run at one time. */
private static final int MAX_JOB_CONTEXTS_COUNT
= ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
@@ -99,7 +99,7 @@
* Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
* things early.
*/
- static final int MIN_CONNECTIVITY_COUNT = 2;
+ static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready.
/**
* Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
* some work early.
@@ -443,13 +443,16 @@
}
/**
- * A job is rescheduled with exponential back-off if the client requests this from their
- * execution logic.
- * A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the
- * timeliness of the reschedule. For an idle-mode job, no deadline is given.
+ * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
+ * specify an override deadline on a failed job (the failed job will run even though it's not
+ * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
+ * ready job with {@link JobStatus#numFailures} > 0 will be executed.
+ *
* @param failureToReschedule Provided job status that we will reschedule.
* @return A newly instantiated JobStatus with the same constraints as the last job except
* with adjusted timing constraints.
+ *
+ * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
*/
private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
final long elapsedNowMillis = SystemClock.elapsedRealtime();
@@ -479,8 +482,9 @@
}
/**
- * Called after a periodic has executed so we can to re-add it. We take the last execution time
- * of the job to be the time of completion (i.e. the time at which this function is called).
+ * Called after a periodic has executed so we can reschedule it. We take the last execution
+ * time of the job to be the time of completion (i.e. the time at which this function is
+ * called).
* This could be inaccurate b/c the job can run for as long as
* {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
* to underscheduling at least, rather than if we had taken the last execution time to be the
@@ -491,7 +495,12 @@
private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
final long elapsedNow = SystemClock.elapsedRealtime();
// Compute how much of the period is remaining.
- long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
+ long runEarly = 0L;
+
+ // If this periodic was rescheduled it won't have a deadline.
+ if (periodicToReschedule.hasDeadlineConstraint()) {
+ runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
+ }
long newEarliestRunTimeElapsed = elapsedNow + runEarly;
long period = periodicToReschedule.getJob().getIntervalMillis();
long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index d26319b..5376043 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -62,13 +62,13 @@
*
*/
public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = JobSchedulerService.DEBUG;
private static final String TAG = "JobServiceContext";
/** Define the maximum # of jobs allowed to run on a service at once. */
private static final int defaultMaxActiveJobsPerService =
ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
/** Amount of time a job is allowed to execute for before being considered timed-out. */
- private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;
+ private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
/** Amount of time the JobScheduler will wait for a response from an app for a message. */
private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
@@ -109,7 +109,13 @@
int mVerb;
private AtomicBoolean mCancelled = new AtomicBoolean();
- /** All the information maintained about the job currently being executed. */
+ /**
+ * All the information maintained about the job currently being executed.
+ *
+ * Any reads (dereferences) not done from the handler thread must be synchronized on
+ * {@link #mLock}.
+ * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
+ */
private JobStatus mRunningJob;
/** Binder to the client service. */
IJobService service;
@@ -194,7 +200,8 @@
*/
JobStatus getRunningJob() {
synchronized (mLock) {
- return mRunningJob;
+ return mRunningJob == null ?
+ null : new JobStatus(mRunningJob);
}
}
@@ -255,15 +262,22 @@
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- if (!name.equals(mRunningJob.getServiceComponent())) {
+ JobStatus runningJob;
+ synchronized (mLock) {
+ // This isn't strictly necessary b/c the JobServiceHandler is running on the main
+ // looper and at this point we can't get any binder callbacks from the client. Better
+ // safe than sorry.
+ runningJob = mRunningJob;
+ }
+ if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
return;
}
this.service = IJobService.Stub.asInterface(service);
final PowerManager pm =
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag());
- mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, runningJob.getTag());
+ mWakeLock.setWorkSource(new WorkSource(runningJob.getUid()));
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
@@ -281,13 +295,15 @@
* @return True if the binder calling is coming from the client we expect.
*/
private boolean verifyCallingUid() {
- if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
- if (DEBUG) {
- Slog.d(TAG, "Stale callback received, ignoring.");
+ synchronized (mLock) {
+ if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stale callback received, ignoring.");
+ }
+ return false;
}
- return false;
+ return true;
}
- return true;
}
/**
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 11e7605..0004c42 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -24,6 +24,7 @@
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.text.format.DateUtils;
import android.util.AtomicFile;
import android.util.ArraySet;
import android.util.Pair;
@@ -552,9 +553,10 @@
return null;
}
- Pair<Long, Long> runtimes;
+ // Tuple of (earliest runtime, latest runtime) in elapsed realtime after disk load.
+ Pair<Long, Long> elapsedRuntimes;
try {
- runtimes = buildExecutionTimesFromXml(parser);
+ elapsedRuntimes = buildExecutionTimesFromXml(parser);
} catch (NumberFormatException e) {
if (DEBUG) {
Slog.d(TAG, "Error parsing execution time parameters, skipping.");
@@ -562,22 +564,45 @@
return null;
}
+ final long elapsedNow = SystemClock.elapsedRealtime();
if (XML_TAG_PERIODIC.equals(parser.getName())) {
try {
String val = parser.getAttributeValue(null, "period");
- jobBuilder.setPeriodic(Long.valueOf(val));
+ final long periodMillis = Long.valueOf(val);
+ jobBuilder.setPeriodic(periodMillis);
+ // As a sanity check, cap the recreated run time to be no later than 2 periods
+ // from now. This is the latest the periodic could be pushed out. This could
+ // happen if the periodic ran early (at the start of its period), and then the
+ // device rebooted.
+ if (elapsedRuntimes.second > elapsedNow + 2 * periodMillis) {
+ final long clampedEarlyRuntimeElapsed = elapsedNow + periodMillis;
+ final long clampedLateRuntimeElapsed = elapsedNow + 2 * periodMillis;
+ Slog.w(TAG,
+ String.format("Periodic job for uid='%d' persisted run-time is" +
+ " too big [%s, %s]. Clamping to [%s,%s]",
+ uid,
+ DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000),
+ DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000),
+ DateUtils.formatElapsedTime(
+ clampedEarlyRuntimeElapsed / 1000),
+ DateUtils.formatElapsedTime(
+ clampedLateRuntimeElapsed / 1000))
+ );
+ elapsedRuntimes =
+ Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
+ }
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
return null;
}
} else if (XML_TAG_ONEOFF.equals(parser.getName())) {
try {
- if (runtimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
- jobBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime());
+ if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
+ jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
}
- if (runtimes.second != JobStatus.NO_LATEST_RUNTIME) {
+ if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
jobBuilder.setOverrideDeadline(
- runtimes.second - SystemClock.elapsedRealtime());
+ elapsedRuntimes.second - elapsedNow);
}
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading job execution criteria, skipping.");
@@ -598,7 +623,8 @@
do {
eventType = parser.next();
} while (eventType == XmlPullParser.TEXT);
- if (!(eventType == XmlPullParser.START_TAG && XML_TAG_EXTRAS.equals(parser.getName()))) {
+ if (!(eventType == XmlPullParser.START_TAG
+ && XML_TAG_EXTRAS.equals(parser.getName()))) {
if (DEBUG) {
Slog.d(TAG, "Error reading extras, skipping.");
}
@@ -609,7 +635,8 @@
jobBuilder.setExtras(extras);
parser.nextTag(); // Consume </extras>
- return new JobStatus(jobBuilder.build(), uid, runtimes.first, runtimes.second);
+ return new JobStatus(
+ jobBuilder.build(), uid, elapsedRuntimes.first, elapsedRuntimes.second);
}
private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 69c63f3..c02611f 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -82,6 +82,13 @@
this.numFailures = numFailures;
}
+ /** Copy constructor. */
+ public JobStatus(JobStatus jobStatus) {
+ this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getNumFailures());
+ this.earliestRunTimeElapsedMillis = jobStatus.getEarliestRunTime();
+ this.latestRunTimeElapsedMillis = jobStatus.getLatestRunTimeElapsed();
+ }
+
/** Create a newly scheduled job. */
public JobStatus(JobInfo job, int uId) {
this(job, uId, 0);
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index cda7c32..21c30c7 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -18,6 +18,7 @@
import android.content.Context;
+import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
@@ -28,7 +29,7 @@
* are ready to run, or whether they must be stopped.
*/
public abstract class StateController {
- protected static final boolean DEBUG = false;
+ protected static final boolean DEBUG = JobSchedulerService.DEBUG;
protected Context mContext;
protected StateChangedListener mStateChangedListener;
protected boolean mDeviceIdleMode;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 8845d6e..7c9c018 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -39,17 +39,17 @@
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
-import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_BACKGROUND_BATTERY_SAVE;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.NetworkPolicyManager.RULE_UNKNOWN;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
-import static android.net.NetworkPolicyManager.dumpPolicy;
-import static android.net.NetworkPolicyManager.dumpRules;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -139,6 +139,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.DebugUtils;
import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.Pair;
@@ -148,8 +149,6 @@
import android.util.TrustedTime;
import android.util.Xml;
-import com.android.server.DeviceIdleController;
-import com.android.server.EventLogTags;
import libcore.io.IoUtils;
import com.android.internal.R;
@@ -157,6 +156,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.DeviceIdleController;
+import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.google.android.collect.Lists;
@@ -285,6 +286,10 @@
final SparseIntArray mUidPolicy = new SparseIntArray();
/** Currently derived rules for each UID. */
final SparseIntArray mUidRules = new SparseIntArray();
+
+ final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
+ final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
+
/** Set of states for the child firewall chains. True if the chain is active. */
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -457,14 +462,8 @@
// read policy from disk
readPolicyLocked();
- if (mRestrictBackground || mRestrictPower || mDeviceIdleMode) {
- updateRulesForGlobalChangeLocked(false);
- updateNotificationsLocked();
- } else {
- // If we are not in any special mode, we just need to make sure the current
- // app idle state is updated.
- updateRulesForAppIdleLocked();
- }
+ updateRulesForGlobalChangeLocked(false);
+ updateNotificationsLocked();
}
updateScreenOn();
@@ -1888,7 +1887,9 @@
if (mDeviceIdleMode != enabled) {
mDeviceIdleMode = enabled;
if (mSystemReady) {
- updateRulesForDeviceIdleLocked();
+ // Device idle change means we need to rebuild rules for all
+ // known apps, so do a global refresh.
+ updateRulesForGlobalChangeLocked(false);
}
if (enabled) {
EventLogTags.writeDeviceIdleOnPhase("net");
@@ -2026,7 +2027,7 @@
fout.print("UID=");
fout.print(uid);
fout.print(" policy=");
- dumpPolicy(fout, policy);
+ fout.print(DebugUtils.flagsToString(NetworkPolicyManager.class, "POLICY_", policy));
fout.println();
}
fout.decreaseIndent();
@@ -2071,18 +2072,14 @@
fout.print("UID=");
fout.print(uid);
- int state = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ final int state = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
fout.print(" state=");
fout.print(state);
fout.print(state <= ActivityManager.PROCESS_STATE_TOP ? " (fg)" : " (bg)");
- fout.print(" rules=");
- final int rulesIndex = mUidRules.indexOfKey(uid);
- if (rulesIndex < 0) {
- fout.print("UNKNOWN");
- } else {
- dumpRules(fout, mUidRules.valueAt(rulesIndex));
- }
+ final int rule = mUidRules.get(uid, RULE_UNKNOWN);
+ fout.print(" rule=");
+ fout.print(DebugUtils.valueToString(NetworkPolicyManager.class, "RULE_", rule));
fout.println();
}
@@ -2117,7 +2114,7 @@
updateRulesForUidStateChangeLocked(uid, oldUidState, uidState);
if (mDeviceIdleMode && isProcStateAllowedWhileIdle(oldUidState)
!= isProcStateAllowedWhileIdle(uidState)) {
- updateRulesForDeviceIdleLocked();
+ updateRuleForDeviceIdleLocked(uid);
}
}
}
@@ -2131,7 +2128,7 @@
updateRulesForUidStateChangeLocked(uid, oldUidState,
ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (mDeviceIdleMode) {
- updateRulesForDeviceIdleLocked();
+ updateRuleForDeviceIdleLocked(uid);
}
}
}
@@ -2178,7 +2175,8 @@
if (mDeviceIdleMode) {
// sync the whitelists before enable dozable chain. We don't care about the rules if
// we are disabling the chain.
- SparseIntArray uidRules = new SparseIntArray();
+ final SparseIntArray uidRules = mUidFirewallDozableRules;
+ uidRules.clear();
final List<UserInfo> users = mUserManager.getUsers();
for (int ui = users.size() - 1; ui >= 0; ui--) {
UserInfo user = users.get(ui);
@@ -2202,6 +2200,7 @@
}
setUidFirewallRules(FIREWALL_CHAIN_DOZABLE, uidRules);
}
+
enableFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mDeviceIdleMode);
}
@@ -2215,11 +2214,15 @@
setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, uid, FIREWALL_RULE_DEFAULT);
}
}
+
+ updateRulesForUidLocked(uid);
}
void updateRulesForAppIdleLocked() {
+ final SparseIntArray uidRules = mUidFirewallStandbyRules;
+ uidRules.clear();
+
// Fully update the app idle firewall chain.
- SparseIntArray uidRules = new SparseIntArray();
final List<UserInfo> users = mUserManager.getUsers();
for (int ui = users.size() - 1; ui >= 0; ui--) {
UserInfo user = users.get(ui);
@@ -2230,6 +2233,7 @@
}
}
}
+
setUidFirewallRules(FIREWALL_CHAIN_STANDBY, uidRules);
}
@@ -2242,11 +2246,14 @@
} else {
setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
}
+
+ updateRulesForUidLocked(uid);
}
void updateRulesForAppIdleParoleLocked() {
boolean enableChain = !mUsageStats.isAppIdleParoleOn();
enableFirewallChainLocked(FIREWALL_CHAIN_STANDBY, enableChain);
+ updateRulesForUidsLocked(mUidFirewallStandbyRules);
}
/**
@@ -2316,6 +2323,12 @@
return true;
}
+ void updateRulesForUidsLocked(SparseIntArray uids) {
+ for (int i = 0; i < uids.size(); i++) {
+ updateRulesForUidLocked(uids.keyAt(i));
+ }
+ }
+
/**
* Applies network rules to bandwidth and firewall controllers based on uid policy.
* @param uid The uid for which to apply the latest policy
@@ -2337,8 +2350,7 @@
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
final boolean uidForeground = isUidForegroundLocked(uid);
- // derive active rules based on policy and active state
-
+ // Derive active rules based on policy and active state
int appId = UserHandle.getAppId(uid);
int uidRules = RULE_ALLOW_ALL;
if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
@@ -2361,20 +2373,27 @@
}
}
- final int oldRules = mUidRules.get(uid);
+ // Check dozable state, which is whitelist
+ if (mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)
+ && mUidFirewallDozableRules.get(uid, FIREWALL_RULE_DEFAULT) != FIREWALL_RULE_ALLOW) {
+ uidRules = RULE_REJECT_ALL;
+ }
+ // Check standby state, which is blacklist
+ if (mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)
+ && mUidFirewallStandbyRules.get(uid, FIREWALL_RULE_DEFAULT) == FIREWALL_RULE_DENY) {
+ uidRules = RULE_REJECT_ALL;
+ }
+
+ final int oldRules = mUidRules.get(uid);
if (uidRules == RULE_ALLOW_ALL) {
mUidRules.delete(uid);
} else {
mUidRules.put(uid, uidRules);
}
- // Update bandwidth rules if necessary
- final boolean oldRejectMetered = (oldRules & RULE_REJECT_METERED) != 0;
- final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0;
- if (oldRejectMetered != rejectMetered) {
- setUidNetworkRules(uid, rejectMetered);
- }
+ final boolean rejectMetered = (uidRules == RULE_REJECT_METERED);
+ setUidNetworkRules(uid, rejectMetered);
// dispatch changed rule to existing listeners
if (oldRules != uidRules) {
@@ -2560,6 +2579,12 @@
* Add or remove a uid to the firewall blacklist for all network ifaces.
*/
private void setUidFirewallRule(int chain, int uid, int rule) {
+ if (chain == FIREWALL_CHAIN_DOZABLE) {
+ mUidFirewallDozableRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_STANDBY) {
+ mUidFirewallStandbyRules.put(uid, rule);
+ }
+
try {
mNetworkManager.setFirewallUidRule(chain, uid, rule);
} catch (IllegalStateException e) {
@@ -2573,9 +2598,9 @@
* Add or remove a uid to the firewall blacklist for all network ifaces.
*/
private void enableFirewallChainLocked(int chain, boolean enable) {
- if (mFirewallChainStates.indexOfKey(chain) >= 0 &&
- mFirewallChainStates.get(chain) == enable) {
- // All is the same, nothing to do.
+ if (mFirewallChainStates.get(chain, false) == enable) {
+ // All is the same, nothing to do. This relies on the fact that netd has child
+ // chains default detached.
return;
}
mFirewallChainStates.put(chain, enable);
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index b504605..8abc8fc 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -571,6 +571,26 @@
grantRuntimePermissionsLPw(musicPackage, STORAGE_PERMISSIONS, userId);
}
+ // Android Wear Home
+ if (mService.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
+
+ PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackageLPr(
+ homeIntent, userId);
+
+ if (wearHomePackage != null
+ && doesPackageSupportRuntimePermissions(wearHomePackage)) {
+ grantRuntimePermissionsLPw(wearHomePackage, CONTACTS_PERMISSIONS, false,
+ userId);
+ grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
+ grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
+ userId);
+ grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
+ userId);
+ }
+ }
+
mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
}
}
@@ -578,7 +598,10 @@
private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(
PackageParser.Package dialerPackage, int userId) {
if (doesPackageSupportRuntimePermissions(dialerPackage)) {
- grantRuntimePermissionsLPw(dialerPackage, PHONE_PERMISSIONS, userId);
+ boolean isPhonePermFixed =
+ mService.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ grantRuntimePermissionsLPw(
+ dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);
grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);
grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f9b2c8..f95ccea 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2285,7 +2285,7 @@
+ mSdkVersion + "; regranting permissions for internal storage");
updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
- updatePermissionsLPw(null, null, updateFlags);
+ updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
ver.sdkVersion = mSdkVersion;
// If this is the first boot or an update from pre-M, and it is a normal
@@ -4775,18 +4775,13 @@
// First try to add the "always" resolution(s) for the current user, if any
if (alwaysList.size() > 0) {
result.addAll(alwaysList);
- // if there is an "always" for the parent user, add it.
- } else if (xpDomainInfo != null && xpDomainInfo.bestDomainVerificationStatus
- == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- result.add(xpDomainInfo.resolveInfo);
} else {
// Add all undefined apps as we want them to appear in the disambiguation dialog.
result.addAll(undefinedList);
+ // Maybe add one for the other profile.
if (xpDomainInfo != null && (
xpDomainInfo.bestDomainVerificationStatus
- == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
- || xpDomainInfo.bestDomainVerificationStatus
- == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK)) {
+ != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
result.add(xpDomainInfo.resolveInfo);
}
includeBrowser = true;
@@ -7554,8 +7549,8 @@
// We would never need to extract libs for forward-locked and external packages,
// since the container service will do it for us. We shouldn't attempt to
// extract libs from system app when it was not updated.
- if (pkg.isForwardLocked() || isExternal(pkg) ||
- (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) ) {
+ if (pkg.isForwardLocked() || pkg.applicationInfo.isExternalAsec() ||
+ (isSystemApp(pkg) && !pkg.isUpdatedSystemApp())) {
extractLibs = false;
}
@@ -7832,7 +7827,7 @@
final String codePath = pkg.codePath;
final File codeFile = new File(codePath);
final boolean bundledApp = info.isSystemApp() && !info.isUpdatedSystemApp();
- final boolean asecApp = info.isForwardLocked() || isExternal(info);
+ final boolean asecApp = info.isForwardLocked() || info.isExternalAsec();
info.nativeLibraryRootDir = null;
info.nativeLibraryRootRequiresIsa = false;
@@ -8230,8 +8225,14 @@
static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+ private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
+ int flags) {
+ final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
+ updatePermissionsLPw(changingPkg, pkgInfo, volumeUuid, flags);
+ }
+
private void updatePermissionsLPw(String changingPkg,
- PackageParser.Package pkgInfo, int flags) {
+ PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
// Make sure there are no dangling permission trees.
Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
while (it.hasNext()) {
@@ -8300,14 +8301,21 @@
if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
for (PackageParser.Package pkg : mPackages.values()) {
if (pkg != pkgInfo) {
- grantPermissionsLPw(pkg, (flags&UPDATE_PERMISSIONS_REPLACE_ALL) != 0,
- changingPkg);
+ // Only replace for packages on requested volume
+ final String volumeUuid = getVolumeUuidForPackage(pkg);
+ final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
+ && Objects.equals(replaceVolumeUuid, volumeUuid);
+ grantPermissionsLPw(pkg, replace, changingPkg);
}
}
}
if (pkgInfo != null) {
- grantPermissionsLPw(pkgInfo, (flags&UPDATE_PERMISSIONS_REPLACE_PKG) != 0, changingPkg);
+ // Only replace for packages on requested volume
+ final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
+ final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
+ && Objects.equals(replaceVolumeUuid, volumeUuid);
+ grantPermissionsLPw(pkgInfo, replace, changingPkg);
}
}
@@ -8334,6 +8342,7 @@
final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+ boolean runtimePermissionsRevoked = false;
int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY;
boolean changedInstallPermission = false;
@@ -8343,6 +8352,17 @@
if (!ps.isSharedUser()) {
origPermissions = new PermissionsState(permissionsState);
permissionsState.reset();
+ } else {
+ // We need to know only about runtime permission changes since the
+ // calling code always writes the install permissions state but
+ // the runtime ones are written only if changed. The only cases of
+ // changed runtime permissions here are promotion of an install to
+ // runtime and revocation of a runtime from a shared user.
+ changedRuntimePermissionUserIds = revokeUnusedSharedUserPermissionsLPw(
+ ps.sharedUser, UserManagerService.getInstance().getUserIds());
+ if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
+ runtimePermissionsRevoked = true;
+ }
}
}
@@ -8558,9 +8578,11 @@
ps.installPermissionsFixed = true;
}
- // Persist the runtime permissions state for users with changes.
+ // Persist the runtime permissions state for users with changes. If permissions
+ // were revoked because no app in the shared user declares them we have to
+ // write synchronously to avoid losing runtime permissions state.
for (int userId : changedRuntimePermissionUserIds) {
- mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+ mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
}
}
@@ -12057,6 +12079,66 @@
}
}
+ private int[] revokeUnusedSharedUserPermissionsLPw(SharedUserSetting su, int[] allUserIds) {
+ // Collect all used permissions in the UID
+ ArraySet<String> usedPermissions = new ArraySet<>();
+ final int packageCount = su.packages.size();
+ for (int i = 0; i < packageCount; i++) {
+ PackageSetting ps = su.packages.valueAt(i);
+ if (ps.pkg == null) {
+ continue;
+ }
+ final int requestedPermCount = ps.pkg.requestedPermissions.size();
+ for (int j = 0; j < requestedPermCount; j++) {
+ String permission = ps.pkg.requestedPermissions.get(j);
+ BasePermission bp = mSettings.mPermissions.get(permission);
+ if (bp != null) {
+ usedPermissions.add(permission);
+ }
+ }
+ }
+
+ PermissionsState permissionsState = su.getPermissionsState();
+ // Prune install permissions
+ List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
+ final int installPermCount = installPermStates.size();
+ for (int i = installPermCount - 1; i >= 0; i--) {
+ PermissionState permissionState = installPermStates.get(i);
+ if (!usedPermissions.contains(permissionState.getName())) {
+ BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
+ if (bp != null) {
+ permissionsState.revokeInstallPermission(bp);
+ permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ }
+ }
+ }
+
+ int[] runtimePermissionChangedUserIds = EmptyArray.INT;
+
+ // Prune runtime permissions
+ for (int userId : allUserIds) {
+ List<PermissionState> runtimePermStates = permissionsState
+ .getRuntimePermissionStates(userId);
+ final int runtimePermCount = runtimePermStates.size();
+ for (int i = runtimePermCount - 1; i >= 0; i--) {
+ PermissionState permissionState = runtimePermStates.get(i);
+ if (!usedPermissions.contains(permissionState.getName())) {
+ BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
+ if (bp != null) {
+ permissionsState.revokeRuntimePermission(bp, userId);
+ permissionsState.updatePermissionFlags(bp, userId,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ runtimePermissionChangedUserIds = ArrayUtils.appendInt(
+ runtimePermissionChangedUserIds, userId);
+ }
+ }
+ }
+ }
+
+ return runtimePermissionChangedUserIds;
+ }
+
private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res,
UserHandle user) {
@@ -12554,6 +12636,18 @@
return installFlags;
}
+ private String getVolumeUuidForPackage(PackageParser.Package pkg) {
+ if (isExternal(pkg)) {
+ if (TextUtils.isEmpty(pkg.volumeUuid)) {
+ return StorageManager.UUID_PRIMARY_PHYSICAL;
+ } else {
+ return pkg.volumeUuid;
+ }
+ } else {
+ return StorageManager.UUID_PRIVATE_INTERNAL;
+ }
+ }
+
private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) {
if (isExternal(pkg)) {
if (TextUtils.isEmpty(pkg.volumeUuid)) {
@@ -13576,7 +13670,7 @@
if (ps != null) {
libDirRoot = ps.legacyNativeLibraryPathString;
}
- if (p != null && (isExternal(p) || p.isForwardLocked())) {
+ if (p != null && (p.isForwardLocked() || p.applicationInfo.isExternalAsec())) {
final long token = Binder.clearCallingIdentity();
try {
String secureContainerId = cidFromCodePath(p.applicationInfo.getBaseCodePath());
@@ -15430,7 +15524,7 @@
if (isMounted) {
if (DEBUG_SD_INSTALL)
Log.i(TAG, "Loading packages");
- loadMediaPackages(processCids, uidArr);
+ loadMediaPackages(processCids, uidArr, externalStorage);
startCleaningPackages();
mInstallerService.onSecureContainersAvailable();
} else {
@@ -15485,7 +15579,8 @@
* the cid is added to list of removeCids. We currently don't delete stale
* containers.
*/
- private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr) {
+ private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr,
+ boolean externalStorage) {
ArrayList<String> pkgList = new ArrayList<String>();
Set<AsecInstallArgs> keys = processCids.keySet();
@@ -15557,7 +15652,10 @@
// cases get permissions that the user didn't initially explicitly
// allow... it would be nice to have some better way to handle
// this situation.
- final VersionInfo ver = mSettings.getExternalVersion();
+ final VersionInfo ver = externalStorage ? mSettings.getExternalVersion()
+ : mSettings.getInternalVersion();
+ final String volumeUuid = externalStorage ? StorageManager.UUID_PRIMARY_PHYSICAL
+ : StorageManager.UUID_PRIVATE_INTERNAL;
int updateFlags = UPDATE_PERMISSIONS_ALL;
if (ver.sdkVersion != mSdkVersion) {
@@ -15565,7 +15663,7 @@
+ mSdkVersion + "; regranting permissions for external");
updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
- updatePermissionsLPw(null, null, updateFlags);
+ updatePermissionsLPw(null, null, volumeUuid, updateFlags);
// Yay, everything is now upgraded
ver.forceCurrent();
@@ -15698,7 +15796,7 @@
+ mSdkVersion + "; regranting permissions for " + vol.fsUuid);
updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
- updatePermissionsLPw(null, null, updateFlags);
+ updatePermissionsLPw(null, null, vol.fsUuid, updateFlags);
// Yay, everything is now upgraded
ver.forceCurrent();
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 571be0e..4093e20 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -517,7 +517,18 @@
ArrayList<String> removeStage = new ArrayList<String>();
for (Map.Entry<String,SharedUserSetting> entry : mSharedUsers.entrySet()) {
final SharedUserSetting sus = entry.getValue();
- if (sus == null || sus.packages.size() == 0) {
+ if (sus == null) {
+ removeStage.add(entry.getKey());
+ continue;
+ }
+ // remove packages that are no longer installed
+ for (Iterator<PackageSetting> iter = sus.packages.iterator(); iter.hasNext();) {
+ PackageSetting ps = iter.next();
+ if (mPackages.get(ps.name) == null) {
+ iter.remove();
+ }
+ }
+ if (sus.packages.size() == 0) {
removeStage.add(entry.getKey());
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8ad45c9..ace2505 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2995,7 +2995,7 @@
// Display task switcher for ALT-TAB.
if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) {
- if (mRecentAppsHeldModifiers == 0 && !keyguardOn) {
+ if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 7c2da2d..be3e922 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -56,7 +56,7 @@
int screenOffset, int maskThickness) {
mScreenSize = new Point();
display.getSize(mScreenSize);
- if (mScreenSize.x != mScreenSize.y) {
+ if (mScreenSize.x != mScreenSize.y + screenOffset) {
Slog.w(TAG, "Screen dimensions of displayId = " + display.getDisplayId() +
"are not equal, circularMask will not be drawn.");
mDimensionsUnequal = true;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 726d29d..42042b94 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1064,6 +1064,8 @@
mAnimator.getScreenRotationAnimationLocked(displayId);
final boolean screenAnimation =
screenRotationAnimation != null && screenRotationAnimation.isAnimating();
+
+ mHasClipRect = false;
if (selfTransformation || attachedTransformation != null
|| appTransformation != null || screenAnimation) {
// cache often used attributes locally
@@ -1139,7 +1141,6 @@
// transforming since it is more important to have that
// animation be smooth.
mShownAlpha = mAlpha;
- mHasClipRect = false;
if (!mService.mLimitedAlphaCompositing
|| (!PixelFormat.formatHasAlpha(mWin.mAttrs.format)
|| (mWin.isIdentityMatrix(mDsDx, mDtDx, mDsDy, mDtDy)
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 3ecfd55..c6d5a7e 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -27,6 +27,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.XmlResourceParser;
+import android.media.midi.IBluetoothMidiService;
import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceOpenCallback;
import android.media.midi.IMidiDeviceServer;
@@ -394,7 +395,20 @@
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- IMidiDeviceServer server = IMidiDeviceServer.Stub.asInterface(service);
+ IMidiDeviceServer server = null;
+ if (mBluetoothDevice != null) {
+ IBluetoothMidiService mBluetoothMidiService = IBluetoothMidiService.Stub.asInterface(service);
+ try {
+ // We need to explicitly add the device in a separate method
+ // because onBind() is only called once.
+ IBinder deviceBinder = mBluetoothMidiService.addBluetoothDevice(mBluetoothDevice);
+ server = IMidiDeviceServer.Stub.asInterface(deviceBinder);
+ } catch(RemoteException e) {
+ Log.e(TAG, "Could not call addBluetoothDevice()", e);
+ }
+ } else {
+ server = IMidiDeviceServer.Stub.asInterface(service);
+ }
setDeviceServer(server);
}
@@ -411,7 +425,6 @@
intent.setComponent(new ComponentName(
MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE,
MidiManager.BLUETOOTH_MIDI_SERVICE_CLASS));
- intent.putExtra("device", mBluetoothDevice);
} else {
intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
intent.setComponent(
@@ -575,6 +588,8 @@
Client client = getClient(token);
if (client == null) return;
client.addListener(listener);
+ // Let listener know whether any ports are already busy.
+ updateStickyDeviceStatus(client.mUid, listener);
}
@Override
@@ -584,6 +599,25 @@
client.removeListener(listener);
}
+ // Inform listener of the status of all known devices.
+ private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) {
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ // ignore private devices that our client cannot access
+ if (device.isUidAllowed(uid)) {
+ try {
+ MidiDeviceStatus status = device.getDeviceStatus();
+ if (status != null) {
+ listener.onDeviceStatusChanged(status);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+ }
+ }
+ }
+
private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0];
public MidiDeviceInfo[] getDevices() {
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java
index 334f708..df44b11 100644
--- a/services/net/java/android/net/dhcp/DhcpAckPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java
@@ -46,7 +46,7 @@
return s + " ACK: your new IP " + mYourIp +
", netmask " + mSubnetMask +
- ", gateway " + mGateway + dnsServers +
+ ", gateways " + mGateways + dnsServers +
", lease time " + mLeaseTime;
}
@@ -79,7 +79,7 @@
}
addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
- addTlv(buffer, DHCP_ROUTER, mGateway);
+ addTlv(buffer, DHCP_ROUTER, mGateways);
addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index e0d2ac1..28cb114 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -299,6 +299,7 @@
Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
NetworkUtils.protectFromVpn(mUdpSock);
} catch(SocketException|ErrnoException e) {
@@ -308,6 +309,16 @@
return true;
}
+ private boolean connectUdpSock(Inet4Address to) {
+ try {
+ Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
+ return true;
+ } catch (SocketException|ErrnoException e) {
+ Log.e(TAG, "Error connecting UDP socket", e);
+ return false;
+ }
+ }
+
private static void closeQuietly(FileDescriptor fd) {
try {
IoBridge.closeAndSignalBlockedThreads(fd);
@@ -325,7 +336,7 @@
try {
mNMService.setInterfaceConfig(mIfaceName, ifcg);
} catch (RemoteException|IllegalStateException e) {
- Log.e(TAG, "Error configuring IP address : " + e);
+ Log.e(TAG, "Error configuring IP address " + address + ": ", e);
return false;
}
return true;
@@ -345,21 +356,22 @@
public void run() {
maybeLog("Receive thread started");
while (!stopped) {
+ int length = 0; // Or compiler can't tell it's initialized if a parse error occurs.
try {
- int length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
+ length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
DhcpPacket packet = null;
packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
- if (packet != null) {
- maybeLog("Received packet: " + packet);
- sendMessage(CMD_RECEIVED_PACKET, packet);
- } else if (PACKET_DBG) {
- Log.d(TAG,
- "Can't parse packet" + HexDump.dumpHexString(mPacket, 0, length));
- }
+ maybeLog("Received packet: " + packet);
+ sendMessage(CMD_RECEIVED_PACKET, packet);
} catch (IOException|ErrnoException e) {
if (!stopped) {
Log.e(TAG, "Read error", e);
}
+ } catch (DhcpPacket.ParseException e) {
+ Log.e(TAG, "Can't parse packet: " + e.getMessage());
+ if (PACKET_DBG) {
+ Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
+ }
}
}
maybeLog("Receive thread stopped");
@@ -376,8 +388,10 @@
maybeLog("Broadcasting " + description);
Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
} else {
- maybeLog("Unicasting " + description + " to " + to.getHostAddress());
- Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
+ // It's safe to call getpeername here, because we only send unicast packets if we
+ // have an IP address, and we connect the UDP socket in DhcpHaveAddressState#enter.
+ maybeLog("Unicasting " + description + " to " + Os.getpeername(mUdpSock));
+ Os.write(mUdpSock, buf);
}
} catch(ErrnoException|IOException e) {
Log.e(TAG, "Can't send packet: ", e);
@@ -789,6 +803,7 @@
transitionTo(mDhcpBoundState);
}
} else if (packet instanceof DhcpNakPacket) {
+ // TODO: Wait a while before returning into INIT state.
Log.d(TAG, "Received NAK, returning to INIT");
mOffer = null;
transitionTo(mDhcpInitState);
@@ -806,10 +821,8 @@
@Override
public void enter() {
super.enter();
- if (setIpAddress(mDhcpLease.ipAddress)) {
- maybeLog("Configured IP address " + mDhcpLease.ipAddress);
- } else {
- Log.e(TAG, "Failed to configure IP address " + mDhcpLease.ipAddress);
+ if (!setIpAddress(mDhcpLease.ipAddress) ||
+ !connectUdpSock((mDhcpLease.serverAddress))) {
notifyFailure();
// There's likely no point in going into DhcpInitState here, we'll probably just
// repeat the transaction, get the same IP address as before, and fail.
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
index 7ca7100..99154ef 100644
--- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
@@ -48,7 +48,7 @@
}
return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask +
- dnsServers + ", gateway " + mGateway +
+ dnsServers + ", gateways " + mGateways +
" lease time " + mLeaseTime + ", domain " + mDomainName;
}
@@ -81,7 +81,7 @@
}
addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
- addTlv(buffer, DHCP_ROUTER, mGateway);
+ addTlv(buffer, DHCP_ROUTER, mGateways);
addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index cbf8fc2..8927bfa 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -114,6 +114,11 @@
protected static final int MAX_LENGTH = 1500;
/**
+ * The magic cookie that identifies this as a DHCP packet instead of BOOTP.
+ */
+ private static final int DHCP_MAGIC_COOKIE = 0x63825363;
+
+ /**
* DHCP Optional Type: DHCP Subnet Mask
*/
protected static final byte DHCP_SUBNET_MASK = 1;
@@ -123,7 +128,7 @@
* DHCP Optional Type: DHCP Router
*/
protected static final byte DHCP_ROUTER = 3;
- protected Inet4Address mGateway;
+ protected List <Inet4Address> mGateways;
/**
* DHCP Optional Type: DHCP DNS Server
@@ -403,7 +408,7 @@
(HWADDR_LEN - mClientMac.length) // pad addr to 16 bytes
+ 64 // empty server host name (64 bytes)
+ 128); // empty boot file name (128 bytes)
- buf.putInt(0x63825363); // magic number
+ buf.putInt(DHCP_MAGIC_COOKIE); // magic number
finishPacket(buf);
// round up to an even number of octets
@@ -668,6 +673,20 @@
return new String(bytes, 0, length, StandardCharsets.US_ASCII);
}
+ private static boolean isPacketToOrFromClient(short udpSrcPort, short udpDstPort) {
+ return (udpSrcPort == DHCP_CLIENT) || (udpDstPort == DHCP_CLIENT);
+ }
+
+ private static boolean isPacketServerToServer(short udpSrcPort, short udpDstPort) {
+ return (udpSrcPort == DHCP_SERVER) && (udpDstPort == DHCP_SERVER);
+ }
+
+ public static class ParseException extends Exception {
+ public ParseException(String msg, Object... args) {
+ super(String.format(msg, args));
+ }
+ }
+
/**
* Creates a concrete DhcpPacket from the supplied ByteBuffer. The
* buffer may have an L2 encapsulation (which is the full EthernetII
@@ -677,7 +696,7 @@
* A subset of the optional parameters are parsed and are stored
* in object fields.
*/
- public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType)
+ public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException
{
// bootp parameters
int transactionId;
@@ -687,8 +706,8 @@
Inet4Address nextIp;
Inet4Address relayIp;
byte[] clientMac;
- List<Inet4Address> dnsServers = new ArrayList<Inet4Address>();
- Inet4Address gateway = null; // aka router
+ List<Inet4Address> dnsServers = new ArrayList<>();
+ List<Inet4Address> gateways = new ArrayList<>(); // aka router
Inet4Address serverIdentifier = null;
Inet4Address netMask = null;
String message = null;
@@ -720,7 +739,8 @@
// check to see if we need to parse L2, IP, and UDP encaps
if (pktType == ENCAP_L2) {
if (packet.remaining() < MIN_PACKET_LENGTH_L2) {
- return null;
+ throw new ParseException("L2 packet too short, %d < %d",
+ packet.remaining(), MIN_PACKET_LENGTH_L2);
}
byte[] l2dst = new byte[6];
@@ -732,18 +752,20 @@
short l2type = packet.getShort();
if (l2type != OsConstants.ETH_P_IP)
- return null;
+ throw new ParseException("Unexpected L2 type 0x%04x, expected 0x%04x",
+ l2type, OsConstants.ETH_P_IP);
}
if (pktType <= ENCAP_L3) {
if (packet.remaining() < MIN_PACKET_LENGTH_L3) {
- return null;
+ throw new ParseException("L3 packet too short, %d < %d",
+ packet.remaining(), MIN_PACKET_LENGTH_L3);
}
byte ipTypeAndLength = packet.get();
int ipVersion = (ipTypeAndLength & 0xf0) >> 4;
if (ipVersion != 4) {
- return null;
+ throw new ParseException("Invalid IP version %d", ipVersion);
}
// System.out.println("ipType is " + ipType);
@@ -759,8 +781,9 @@
ipSrc = readIpAddress(packet);
ipDst = readIpAddress(packet);
- if (ipProto != IP_TYPE_UDP) // UDP
- return null;
+ if (ipProto != IP_TYPE_UDP) {
+ throw new ParseException("Protocol not UDP: %d", ipProto);
+ }
// Skip options. This cannot cause us to read beyond the end of the buffer because the
// IPv4 header cannot be more than (0x0f * 4) = 60 bytes long, and that is less than
@@ -776,13 +799,19 @@
short udpLen = packet.getShort();
short udpChkSum = packet.getShort();
- if ((udpSrcPort != DHCP_SERVER) && (udpSrcPort != DHCP_CLIENT))
+ // Only accept packets to or from the well-known client port (expressly permitting
+ // packets from ports other than the well-known server port; http://b/24687559), and
+ // server-to-server packets, e.g. for relays.
+ if (!isPacketToOrFromClient(udpSrcPort, udpDstPort) &&
+ !isPacketServerToServer(udpSrcPort, udpDstPort)) {
return null;
+ }
}
// We need to check the length even for ENCAP_L3 because the IPv4 header is variable-length.
if (pktType > ENCAP_BOOTP || packet.remaining() < MIN_PACKET_LENGTH_BOOTP) {
- return null;
+ throw new ParseException("Invalid type or BOOTP packet too short, %d < %d",
+ packet.remaining(), MIN_PACKET_LENGTH_BOOTP);
}
byte type = packet.get();
@@ -805,7 +834,7 @@
packet.get(ipv4addr);
relayIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
} catch (UnknownHostException ex) {
- return null;
+ throw new ParseException("Invalid IPv4 address: %s", Arrays.toString(ipv4addr));
}
// Some DHCP servers have been known to announce invalid client hardware address values such
@@ -828,8 +857,10 @@
int dhcpMagicCookie = packet.getInt();
- if (dhcpMagicCookie != 0x63825363)
- return null;
+ if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) {
+ throw new ParseException("Bad magic cookie 0x%08x, should be 0x%08x", dhcpMagicCookie,
+ DHCP_MAGIC_COOKIE);
+ }
// parse options
boolean notFinishedOptions = true;
@@ -852,8 +883,9 @@
expectedLen = 4;
break;
case DHCP_ROUTER:
- gateway = readIpAddress(packet);
- expectedLen = 4;
+ for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
+ gateways.add(readIpAddress(packet));
+ }
break;
case DHCP_DNS_SERVER:
for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
@@ -937,18 +969,20 @@
}
if (expectedLen != optionLen) {
- return null;
+ throw new ParseException("Invalid length %d for option %d, expected %d",
+ optionLen, optionType, expectedLen);
}
}
} catch (BufferUnderflowException e) {
- return null;
+ throw new ParseException("BufferUnderflowException");
}
}
DhcpPacket newPacket;
switch(dhcpType) {
- case -1: return null;
+ case (byte) 0xFF:
+ throw new ParseException("No DHCP message type option");
case DHCP_MESSAGE_TYPE_DISCOVER:
newPacket = new DhcpDiscoverPacket(
transactionId, secs, clientMac, broadcast);
@@ -981,14 +1015,13 @@
clientMac);
break;
default:
- System.out.println("Unimplemented type: " + dhcpType);
- return null;
+ throw new ParseException("Unimplemented DHCP type %d", dhcpType);
}
newPacket.mBroadcastAddress = bcAddr;
newPacket.mDnsServers = dnsServers;
newPacket.mDomainName = domainName;
- newPacket.mGateway = gateway;
+ newPacket.mGateways = gateways;
newPacket.mHostName = hostName;
newPacket.mLeaseTime = leaseTime;
newPacket.mMessage = message;
@@ -1009,7 +1042,7 @@
* Parse a packet from an array of bytes, stopping at the given length.
*/
public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType)
- {
+ throws ParseException {
ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
return decodeFullPacket(buffer, pktType);
}
@@ -1044,7 +1077,11 @@
} catch (IllegalArgumentException e) {
return null;
}
- results.gateway = mGateway;
+
+ if (mGateways.size() > 0) {
+ results.gateway = mGateways.get(0);
+ }
+
results.dnsServers.addAll(mDnsServers);
results.domains = mDomainName;
results.serverAddress = mServerIdentifier;
@@ -1086,11 +1123,11 @@
public static ByteBuffer buildOfferPacket(int encap, int transactionId,
boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
- Inet4Address gateway, List<Inet4Address> dnsServers,
+ List<Inet4Address> gateways, List<Inet4Address> dnsServers,
Inet4Address dhcpServerIdentifier, String domainName) {
DhcpPacket pkt = new DhcpOfferPacket(
transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
- pkt.mGateway = gateway;
+ pkt.mGateways = gateways;
pkt.mDnsServers = dnsServers;
pkt.mLeaseTime = timeout;
pkt.mDomainName = domainName;
@@ -1106,11 +1143,11 @@
public static ByteBuffer buildAckPacket(int encap, int transactionId,
boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
- Inet4Address gateway, List<Inet4Address> dnsServers,
+ List<Inet4Address> gateways, List<Inet4Address> dnsServers,
Inet4Address dhcpServerIdentifier, String domainName) {
DhcpPacket pkt = new DhcpAckPacket(
transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
- pkt.mGateway = gateway;
+ pkt.mGateways = gateways;
pkt.mDnsServers = dnsServers;
pkt.mLeaseTime = timeout;
pkt.mDomainName = domainName;
diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
index cd3b8bb..7e60bf1 100644
--- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
@@ -117,7 +117,7 @@
private void assertDomainAndVendorInfoParses(
String expectedDomain, byte[] domainBytes,
- String expectedVendorInfo, byte[] vendorInfoBytes) {
+ String expectedVendorInfo, byte[] vendorInfoBytes) throws Exception {
ByteBuffer packet = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER)
.setDomainBytes(domainBytes)
.setVendorInfoBytes(vendorInfoBytes)
@@ -158,17 +158,25 @@
}
private void assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime,
- long leaseTimeMillis, byte[] leaseTimeBytes) {
+ long leaseTimeMillis, byte[] leaseTimeBytes) throws Exception {
TestDhcpPacket testPacket = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER);
if (leaseTimeBytes != null) {
testPacket.setLeaseTimeBytes(leaseTimeBytes);
}
ByteBuffer packet = testPacket.build();
- DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
+ DhcpPacket offerPacket = null;
+
if (!expectValid) {
- assertNull(offerPacket);
+ try {
+ offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
+ fail("Invalid packet parsed successfully: " + offerPacket);
+ } catch (ParseException expected) {
+ }
return;
}
+
+ offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
+ assertNotNull(offerPacket);
assertEquals(rawLeaseTime, offerPacket.mLeaseTime);
DhcpResults dhcpResults = offerPacket.toDhcpResults(); // Just check this doesn't crash.
assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis());
@@ -200,14 +208,14 @@
}
private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp,
- byte[] netmaskBytes) {
+ byte[] netmaskBytes) throws Exception {
checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes);
checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes);
}
private void checkIpAddress(String expected, byte type,
Inet4Address clientIp, Inet4Address yourIp,
- byte[] netmaskBytes) {
+ byte[] netmaskBytes) throws Exception {
ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp)
.setNetmaskBytes(netmaskBytes)
.build();
@@ -506,4 +514,74 @@
assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53",
"lancs.ac.uk", "10.32.255.128", null, 7200, false, dhcpResults);
}
+
+ @SmallTest
+ public void testUdpServerAnySourcePort() throws Exception {
+ final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ // Ethernet header.
+ "9cd917000000001c2e0000000800" +
+ // IP header.
+ "45a00148000040003d115087d18194fb0a0f7af2" +
+ // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
+ // NOTE: The server source port is not the canonical port 67.
+ "C29F004401341268" +
+ // BOOTP header.
+ "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
+ // MAC address.
+ "9cd91700000000000000000000000000" +
+ // Server name.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // File.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // Options.
+ "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
+ "d18180060f0777766d2e6564751c040a0fffffff000000"
+ ).toCharArray(), false));
+
+ DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
+ assertTrue(offerPacket instanceof DhcpOfferPacket);
+ assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac()));
+ DhcpResults dhcpResults = offerPacket.toDhcpResults();
+ assertDhcpResults("10.15.122.242/16", "10.15.200.23",
+ "209.129.128.3,209.129.148.3,209.129.128.6",
+ "wvm.edu", "10.1.105.252", null, 86400, false, dhcpResults);
+ }
+
+ @SmallTest
+ public void testMultipleRouters() throws Exception {
+ final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ // Ethernet header.
+ "fc3d93000000" + "081735000000" + "0800" +
+ // IP header.
+ "45000148c2370000ff117ac2c0a8bd02ffffffff" +
+ // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
+ "0043004401343beb" +
+ // BOOTP header.
+ "0201060027f518e20000800000000000c0a8bd310000000000000000" +
+ // MAC address.
+ "fc3d9300000000000000000000000000" +
+ // Server name.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // File.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // Options.
+ "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
+ "0308c0a8bd01ffffff0006080808080808080404ff000000000000"
+ ).toCharArray(), false));
+
+ DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
+ assertTrue(offerPacket instanceof DhcpOfferPacket);
+ assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac()));
+ DhcpResults dhcpResults = offerPacket.toDhcpResults();
+ assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4",
+ null, "192.171.189.2", null, 28800, false, dhcpResults);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
new file mode 100644
index 0000000..be6861c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -0,0 +1,64 @@
+package com.android.server.content;
+
+import android.os.Bundle;
+
+import junit.framework.TestCase;
+
+public class SyncManagerTest extends TestCase {
+
+ final String KEY_1 = "key_1";
+ final String KEY_2 = "key_2";
+
+ public void testSyncExtrasEquals_WithNull() throws Exception {
+ Bundle b1 = new Bundle();
+ Bundle b2 = new Bundle();
+
+ b1.putString(KEY_1, null);
+ b2.putString(KEY_1, null);
+
+ assertTrue("Null extra not properly compared between bundles.",
+ SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+ }
+
+ public void testSyncExtrasEqualsBigger_WithNull() throws Exception {
+ Bundle b1 = new Bundle();
+ Bundle b2 = new Bundle();
+
+ b1.putString(KEY_1, null);
+ b2.putString(KEY_1, null);
+
+ b1.putString(KEY_2, "bla");
+ b2.putString(KEY_2, "bla");
+
+ assertTrue("Extras not properly compared between bundles.",
+ SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+ }
+
+ public void testSyncExtrasEqualsFails_differentValues() throws Exception {
+ Bundle b1 = new Bundle();
+ Bundle b2 = new Bundle();
+
+ b1.putString(KEY_1, null);
+ b2.putString(KEY_1, null);
+
+ b1.putString(KEY_2, "bla");
+ b2.putString(KEY_2, "ble"); // different key
+
+ assertFalse("Extras considered equal when they are different.",
+ SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+ }
+
+ public void testSyncExtrasEqualsFails_differentNulls() throws Exception {
+ Bundle b1 = new Bundle();
+ Bundle b2 = new Bundle();
+
+ b1.putString(KEY_1, null);
+ b2.putString(KEY_1, "bla"); // different key
+
+ b1.putString(KEY_2, "ble");
+ b2.putString(KEY_2, "ble");
+
+ assertFalse("Extras considered equal when they are different.",
+ SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index bd64392..0b73beb 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,6 +6,7 @@
import android.app.job.JobInfo;
import android.app.job.JobInfo.Builder;
import android.os.PersistableBundle;
+import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.util.Log;
@@ -102,6 +103,14 @@
Iterator<JobStatus> it = jobStatusSet.iterator();
JobStatus loaded1 = it.next();
JobStatus loaded2 = it.next();
+
+ // Reverse them so we know which comparison to make.
+ if (loaded1.getJobId() != 8) {
+ JobStatus tmp = loaded1;
+ loaded1 = loaded2;
+ loaded2 = tmp;
+ }
+
assertTasksEqual(task1, loaded1.getJob());
assertTasksEqual(task2, loaded2.getJob());
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
@@ -143,6 +152,36 @@
assertTasksEqual(task, loaded.getJob());
}
+ public void testMassivePeriodClampedOnRead() throws Exception {
+ final long TEN_SECONDS = 10000L;
+ JobInfo.Builder b = new Builder(8, mComponent)
+ .setPeriodic(TEN_SECONDS)
+ .setPersisted(true);
+ final long invalidLateRuntimeElapsedMillis =
+ SystemClock.elapsedRealtime() + (TEN_SECONDS * 2) + 5000; // >2P from now.
+ final long invalidEarlyRuntimeElapsedMillis =
+ invalidLateRuntimeElapsedMillis - TEN_SECONDS; // Early is (late - period).
+ final JobStatus js = new JobStatus(b.build(), SOME_UID,
+ invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis);
+
+ mTaskStoreUnderTest.add(js);
+ Thread.sleep(IO_WAIT);
+
+ final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
+ assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+ JobStatus loaded = jobStatusSet.iterator().next();
+
+ // Assert early runtime was clamped to be under now + period. We can do <= here b/c we'll
+ // call SystemClock.elapsedRealtime after doing the disk i/o.
+ final long newNowElapsed = SystemClock.elapsedRealtime();
+ assertTrue("Early runtime wasn't correctly clamped.",
+ loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS);
+ // Assert late runtime was clamped to be now + period*2.
+ assertTrue("Early runtime wasn't correctly clamped.",
+ loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS*2);
+ }
+
/**
* Helper function to throw an error if the provided task and TaskStatus objects are not equal.
*/
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4146c1c0..5ad796f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -314,6 +314,8 @@
mAppIdleParoled = paroled;
if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleParoled);
if (paroled) {
+ postParoleEndTimeout();
+ } else {
mLastAppIdleParoledTime = checkAndGetTimeLocked();
postNextParoleTimeout();
}
@@ -404,8 +406,6 @@
if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
setAppIdleParoled(true);
- // Make sure it ends at some point
- postParoleEndTimeout();
} else {
if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
postNextParoleTimeout();
@@ -492,7 +492,6 @@
if (!deviceIdle
&& timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
if (DEBUG) Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
- postNextParoleTimeout();
setAppIdleParoled(true);
} else if (deviceIdle) {
if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
index 3ca0c84..31d859f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -87,6 +87,7 @@
// This is an indirect indication of the microphone being open in some other application.
private boolean mServiceDisabled = false;
private boolean mStarted = false;
+ private boolean mRecognitionAborted = false;
private PowerSaveModeListener mPowerSaveModeListener;
SoundTriggerHelper(Context context) {
@@ -386,8 +387,9 @@
private void onRecognitionAbortLocked() {
Slog.w(TAG, "Recognition aborted");
- // No-op
- // This is handled via service state changes instead.
+ // If abort has been called, the hardware has already stopped recognition, so we shouldn't
+ // call it again when we process the state change.
+ mRecognitionAborted = true;
}
private void onRecognitionFailureLocked() {
@@ -490,8 +492,13 @@
}
return status;
} else {
- // Stop recognition.
- int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+ // Stop recognition (only if we haven't been aborted).
+ int status = STATUS_OK;
+ if (!mRecognitionAborted) {
+ status = mModule.stopRecognition(mCurrentSoundModelHandle);
+ } else {
+ mRecognitionAborted = false;
+ }
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "stopRecognition call failed with " + status);
if (notify) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a8874d0..a96c164 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -925,7 +925,7 @@
return;
}
synchronized (this) {
- pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n");
+ pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)");
pw.println(" mEnableService: " + mEnableService);
if (mImpl == null) {
pw.println(" (No active implementation)");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 28520be..30296e1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -36,6 +36,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
+import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.view.IWindowManager;
@@ -114,9 +115,9 @@
mAm = ActivityManagerNative.getDefault();
VoiceInteractionServiceInfo info;
try {
- info = new VoiceInteractionServiceInfo(context.getPackageManager(), service);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Voice interaction service not found: " + service);
+ info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser);
+ } catch (RemoteException|PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Voice interaction service not found: " + service, e);
mInfo = null;
mSessionComponentName = null;
mIWindowManager = null;
@@ -260,9 +261,18 @@
}
return;
}
+ pw.print(" mUser="); pw.println(mUser);
pw.print(" mComponent="); pw.println(mComponent.flattenToShortString());
pw.print(" Session service="); pw.println(mInfo.getSessionService());
+ pw.println(" Service info:");
+ mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), " ");
+ pw.println(" Application info:");
+ mInfo.getServiceInfo().applicationInfo.dump(new PrintWriterPrinter(pw), " ");
+ pw.print(" Recognition service="); pw.println(mInfo.getRecognitionService());
pw.print(" Settings activity="); pw.println(mInfo.getSettingsActivity());
+ pw.print(" Supports assist="); pw.println(mInfo.getSupportsAssist());
+ pw.print(" Supports launch from keyguard=");
+ pw.println(mInfo.getSupportsLaunchFromKeyguard());
if (mDisabledShowContext != 0) {
pw.print(" mDisabledShowContext=");
pw.println(Integer.toHexString(mDisabledShowContext));
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index cdb0bf2..b64043c 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -121,6 +121,14 @@
public static final int CAPABILITY_CALL_SUBJECT = 0x40;
/**
+ * Flag indicating that this {@code PhoneAccount} should only be used for emergency calls.
+ * <p>
+ * See {@link #getCapabilities}
+ * @hide
+ */
+ public static final int CAPABILITY_EMERGENCY_CALLS_ONLY = 0x80;
+
+ /**
* URI scheme for telephone number URIs.
*/
public static final String SCHEME_TEL = "tel";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index aa4da4b..29e54a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -191,7 +191,7 @@
public static final String
KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
- /**
+ /**
* Override the platform's notion of a network operator being considered roaming.
* Value is string array of MCCMNCs to be considered roaming for 3GPP RATs.
*/
@@ -268,6 +268,13 @@
= "carrier_allow_turnoff_ims_bool";
/**
+ * Flag specifying whether Generic Bootstrapping Architecture capable SIM is required for IMS.
+ * @hide
+ */
+ public static final String KEY_CARRIER_IMS_GBA_REQUIRED_BOOL
+ = "carrier_ims_gba_required_bool";
+
+ /**
* Flag specifying whether IMS instant lettering is available for the carrier. {@code True} if
* instant lettering is available for the carrier, {@code false} otherwise.
* @hide
@@ -380,9 +387,9 @@
"ci_action_on_sys_update_extra_val_string";
/**
- * Specifies the amount of gap to be added in millis between DTMF tones. When a non-zero value
- * is specified, the UE shall wait for the specified amount of time before it sends out
- * successive DTMF tones on the network.
+ * Specifies the amount of gap to be added in millis between postdial DTMF tones. When a
+ * non-zero value is specified, the UE shall wait for the specified amount of time before it
+ * sends out successive DTMF tones on the network.
* @hide
*/
public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -421,6 +428,14 @@
public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
/**
+ * Specifies the amount of gap to be added in millis between postdial DTMF tones. When a
+ * non-zero value is specified, the UE shall wait for the specified amount of time before it
+ * sends out successive DTMF tones on the network.
+ * @hide
+ */
+ public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
+
+ /**
* If this is true, the SIM card (through Customer Service Profile EF file) will be able to
* prevent manual operator selection. If false, this SIM setting will be ignored and manual
* operator selection will always be available. See CPHS4_2.WW6, CPHS B.4.7.1 for more
@@ -428,6 +443,12 @@
*/
public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+ /**
+ * Allow user to add APNs
+ * @hide
+ */
+ public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
+
// These variables are used by the MMS service and exposed through another API, {@link
// SmsManager}. The variable names and string values are copied from there.
public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
@@ -489,6 +510,7 @@
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
+ sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING, "");
sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING, "");
@@ -524,6 +546,7 @@
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING, "");
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, "");
sDefaults.putBoolean(KEY_CSP_ENABLED_BOOL, false);
+ sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
@@ -537,6 +560,7 @@
sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
+ sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
// MMS defaults
sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false);
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index f535e5d..fced667 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -911,7 +911,7 @@
else if (tdScdmaDbm >= -49) level = SIGNAL_STRENGTH_GREAT;
else if (tdScdmaDbm >= -73) level = SIGNAL_STRENGTH_GOOD;
else if (tdScdmaDbm >= -97) level = SIGNAL_STRENGTH_MODERATE;
- else if (tdScdmaDbm >= -120) level = SIGNAL_STRENGTH_POOR;
+ else if (tdScdmaDbm >= -110) level = SIGNAL_STRENGTH_POOR;
else level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
if (DBG) log("getTdScdmaLevel = " + level);
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 37ffa06..e11c8d3 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -334,7 +334,8 @@
@Override
public String toString() {
- return "{id=" + mId + ", iccId=" + mIccId + " simSlotIndex=" + mSimSlotIndex
+ String iccIdToPrint = mIccId != null ? mIccId.substring(0, 9) + "XXXXXXXXXXX" : null;
+ return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
+ " displayName=" + mDisplayName + " carrierName=" + mCarrierName
+ " nameSource=" + mNameSource + " iconTint=" + mIconTint
+ " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d22727d..6b1b6296 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2874,7 +2874,7 @@
/**
* Returns all observed cell information from all radios on the
* device including the primary and neighboring cells. This does
- * not cause or change the rate of PhoneStateListner#onCellInfoChanged.
+ * not cause or change the rate of PhoneStateListener#onCellInfoChanged.
*<p>
* The list can include one or more of {@link android.telephony.CellInfoGsm CellInfoGsm},
* {@link android.telephony.CellInfoCdma CellInfoCdma},
@@ -2888,6 +2888,9 @@
* devices this may return null in which case getCellLocation should
* be called.
*<p>
+ * This API will return valid data for registered cells on devices with
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}
+ *<p>
* @return List of CellInfo or null if info unavailable.
*
* <p>Requires Permission: {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
diff --git a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
index 49ee642..2e44a77 100644
--- a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
@@ -29,9 +29,6 @@
import android.view.ViewGroup;
import android.widget.ListView;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Delegate that provides implementation for native methods in {@link Preference}
* <p/>
@@ -59,9 +56,9 @@
*/
public static View inflatePreference(Context context, XmlPullParser parser, ViewGroup root) {
PreferenceManager pm = new PreferenceManager(context);
- PreferenceScreen ps = pm.getPreferenceScreen();
PreferenceInflater inflater = new BridgePreferenceInflater(context, pm);
- ps = (PreferenceScreen) inflater.inflate(parser, ps, true);
+ PreferenceScreen ps = (PreferenceScreen) inflater.inflate(parser, null, true);
+ pm.setPreferences(ps);
ListView preferenceView = createContainerView(context, root);
ps.bind(preferenceView);
return preferenceView;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e611ea4..59b22bd 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -101,9 +101,18 @@
public static final String REALM_KEY = "realm";
/** @hide */
public static final String PLMN_KEY = "plmn";
+ /** @hide */
+ public static final String PHASE1_KEY = "phase1";
+ /** {@hide} */
+ public static final String ENABLE_TLS_1_2 = "\"tls_disable_tlsv1_2=0\"";
+ /** {@hide} */
+ public static final String DISABLE_TLS_1_2 = "\"tls_disable_tlsv1_2=1\"";
private HashMap<String, String> mFields = new HashMap<String, String>();
+ //By default, we enable TLS1.2. However, due to a known bug on some radius, we may disable it to
+ // fall back to TLS 1.1.
+ private boolean mTls12Enable = true;
private X509Certificate mCaCert;
private PrivateKey mClientPrivateKey;
private X509Certificate mClientCertificate;
@@ -149,6 +158,7 @@
}
writeCertificate(dest, mClientCertificate);
+ dest.writeInt(mTls12Enable ? 1: 0);
}
private void writeCertificate(Parcel dest, X509Certificate cert) {
@@ -196,6 +206,7 @@
enterpriseConfig.mClientPrivateKey = userKey;
enterpriseConfig.mClientCertificate = readCertificate(in);
+ enterpriseConfig.mTls12Enable = (in.readInt() == 1);
return enterpriseConfig;
}
@@ -300,6 +311,26 @@
}
/**
+ * Set the TLS version
+ * @param enable: true -- enable TLS1.2 false -- disable TLS1.2
+ * @hide
+ */
+ public void setTls12Enable(boolean enable) {
+ mTls12Enable = enable;
+ mFields.put(PHASE1_KEY,
+ enable ? ENABLE_TLS_1_2 : DISABLE_TLS_1_2);
+ }
+
+ /**
+ * Get the TLS1.2 enabled or not
+ * @return eap method configured
+ * @hide
+ */
+ public boolean getTls12Enable() {
+ return mTls12Enable;
+ }
+
+ /**
* Get the eap method.
* @return eap method configured
*/