Merge "Fix inefficient CursorAnchorInfo#hashCode()." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 3444597..fbbb2e3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3578,6 +3578,7 @@
method public final deprecated void removeDialog(int);
method public void reportFullyDrawn();
method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+ method public final void requestKeyboardShortcutsHelper();
method public final void requestPermissions(java.lang.String[], int);
method public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index a4927f2..d91f5d1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3696,6 +3696,7 @@
method public final deprecated void removeDialog(int);
method public void reportFullyDrawn();
method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+ method public final void requestKeyboardShortcutsHelper();
method public final void requestPermissions(java.lang.String[], int);
method public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 4179948..bad9733 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3578,6 +3578,7 @@
method public final deprecated void removeDialog(int);
method public void reportFullyDrawn();
method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+ method public final void requestKeyboardShortcutsHelper();
method public final void requestPermissions(java.lang.String[], int);
method public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 672a706..7652766 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1676,6 +1676,17 @@
public void onProvideAssistContent(AssistContent outContent) {
}
+ /**
+ * Request the Keyboard Shortcuts screen to show up. If it succeeds, this will trigger
+ * {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
+ */
+ public final void requestKeyboardShortcutsHelper() {
+ Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+ intent.setComponent(new ComponentName("com.android.systemui",
+ "com.android.systemui.statusbar.KeyboardShortcutsReceiver"));
+ sendBroadcast(intent);
+ }
+
@Override
public void onProvideKeyboardShortcuts(
List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 207b70a..7e67e8d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1404,6 +1404,16 @@
public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
/**
+ * Activity Action: Start the Keyboard Shortcuts Helper screen.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SHOW_KEYBOARD_SHORTCUTS =
+ "android.intent.action.SHOW_KEYBOARD_SHORTCUTS";
+
+ /**
* Activity Action: Show settings for managing network data usage of a
* specific application. Applications should define an activity that offers
* options to control data usage.
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index e85311d..8236f55 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -36,22 +36,21 @@
* @hide
*/
public class PackageUserState {
+ public long ceDataInode;
+ public boolean installed;
public boolean stopped;
public boolean notLaunched;
- public boolean installed;
public boolean hidden; // Is the app restricted by owner / admin
public boolean suspended;
- public int enabled;
public boolean blockUninstall;
-
+ public int enabled;
public String lastDisableAppCaller;
+ public int domainVerificationStatus;
+ public int appLinkGeneration;
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
- public int domainVerificationStatus;
- public int appLinkGeneration;
-
public PackageUserState() {
installed = true;
hidden = false;
@@ -62,18 +61,19 @@
}
public PackageUserState(PackageUserState o) {
+ ceDataInode = o.ceDataInode;
installed = o.installed;
stopped = o.stopped;
notLaunched = o.notLaunched;
- enabled = o.enabled;
hidden = o.hidden;
suspended = o.suspended;
- lastDisableAppCaller = o.lastDisableAppCaller;
- disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
- enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
blockUninstall = o.blockUninstall;
+ enabled = o.enabled;
+ lastDisableAppCaller = o.lastDisableAppCaller;
domainVerificationStatus = o.domainVerificationStatus;
appLinkGeneration = o.appLinkGeneration;
+ disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
+ enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
}
/**
diff --git a/core/java/com/android/internal/app/procstats/SparseMappingTable.java b/core/java/com/android/internal/app/procstats/SparseMappingTable.java
index cd4f7b6..76102af 100644
--- a/core/java/com/android/internal/app/procstats/SparseMappingTable.java
+++ b/core/java/com/android/internal/app/procstats/SparseMappingTable.java
@@ -629,7 +629,7 @@
* this is an eng build.)
*/
private static void logOrThrow(String message, Throwable th) {
- Slog.wtf(TAG, message, th);
+ Slog.e(TAG, message, th);
if (Build.TYPE.equals("eng")) {
throw new RuntimeException(message, th);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index 85cc841..46b49de 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -285,7 +285,7 @@
}
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
- InputMethodInfo imi, InputMethodSubtype subtype) {
+ InputMethodInfo imi, InputMethodSubtype subtype, boolean forward) {
if (imi == null) {
return null;
}
@@ -297,8 +297,9 @@
return null;
}
final int N = mImeSubtypeList.size();
- for (int offset = 1; offset < N; ++offset) {
+ for (int i = 1; i < N; ++i) {
// Start searching the next IME/subtype from the next of the current index.
+ final int offset = forward ? i : N - i;
final int candidateIndex = (currentIndex + offset) % N;
final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex);
// Skip if searching inside the current IME only, but the candidate is not
@@ -371,7 +372,7 @@
}
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
- InputMethodInfo imi, InputMethodSubtype subtype) {
+ InputMethodInfo imi, InputMethodSubtype subtype, boolean forward) {
int currentUsageRank = getUsageRank(imi, subtype);
if (currentUsageRank < 0) {
if (DEBUG) {
@@ -381,7 +382,8 @@
}
final int N = mUsageHistoryOfSubtypeListItemIndex.length;
for (int i = 1; i < N; i++) {
- final int subtypeListItemRank = (currentUsageRank + i) % N;
+ final int offset = forward ? i : N - i;
+ final int subtypeListItemRank = (currentUsageRank + offset) % N;
final int subtypeListItemIndex =
mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
final ImeSubtypeListItem subtypeListItem =
@@ -455,16 +457,16 @@
}
public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi,
- InputMethodSubtype subtype) {
+ InputMethodSubtype subtype, boolean forward) {
if (imi == null) {
return null;
}
if (imi.supportsSwitchingToNextInputMethod()) {
return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
- subtype);
+ subtype, forward);
} else {
return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
- subtype);
+ subtype, forward);
}
}
@@ -532,14 +534,14 @@
}
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
- InputMethodSubtype subtype) {
+ InputMethodSubtype subtype, boolean forward) {
if (mController == null) {
if (DEBUG) {
Log.e(TAG, "mController shouldn't be null.");
}
return null;
}
- return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
+ return mController.getNextInputMethod(onlyCurrentIme, imi, subtype, forward);
}
public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index 2a9264d..47f2c70 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -103,21 +103,7 @@
}
}
- public void execute(String cmd, Object... args) throws InstallerException {
- final String resRaw = executeForResult(cmd, args);
- int res = -1;
- try {
- res = Integer.parseInt(resRaw);
- } catch (NumberFormatException ignored) {
- }
- if (res != 0) {
- throw new InstallerException(
- "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
- }
- }
-
- public String executeForResult(String cmd, Object... args)
- throws InstallerException {
+ public String[] execute(String cmd, Object... args) throws InstallerException {
final StringBuilder builder = new StringBuilder(cmd);
for (Object arg : args) {
String escaped;
@@ -135,7 +121,17 @@
}
builder.append(' ').append(escaped);
}
- return transact(builder.toString());
+ final String[] resRaw = transact(builder.toString()).split(" ");
+ int res = -1;
+ try {
+ res = Integer.parseInt(resRaw[0]);
+ } catch (ArrayIndexOutOfBoundsException | NumberFormatException ignored) {
+ }
+ if (res != 0) {
+ throw new InstallerException(
+ "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
+ }
+ return resRaw;
}
public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded,
@@ -160,19 +156,15 @@
}
public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
- String rawReply = executeForResult("merge_profiles", uid, pkgName);
- if (rawReply == null) {
- throw new IllegalStateException("Unexpected null reply");
- }
- final String res[] = rawReply.split(" ");
+ final String[] res = execute("merge_profiles", uid, pkgName);
if ((res == null) || (res.length != 2)) {
- throw new InstallerException("Invalid size result: " + rawReply);
+ throw new InstallerException("Invalid size result: " + Arrays.toString(res));
}
// Just as a sanity check. Anything != "true" will be interpreted as false by parseBoolean.
if (!res[1].equals("true") && !res[1].equals("false")) {
- throw new InstallerException("Invalid boolean result: " + rawReply);
+ throw new InstallerException("Invalid boolean result: " + Arrays.toString(res));
}
return Boolean.parseBoolean(res[1]);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index bed5a2e..18f715e 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -27,6 +27,7 @@
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -128,7 +129,7 @@
/**
* Checks if given array is null or has zero elements.
*/
- public static boolean isEmpty(@Nullable List<?> array) {
+ public static boolean isEmpty(@Nullable Collection<?> array) {
return array == null || array.isEmpty();
}
@@ -451,7 +452,7 @@
}
}
- public static <T> boolean contains(@Nullable ArrayList<T> cur, T val) {
+ public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
return (cur != null) ? cur.contains(val) : false;
}
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index ec5220f..ba5206a 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -27,7 +27,6 @@
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.internal.inputmethod.InputMethodUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -68,7 +67,7 @@
ri.serviceInfo = si;
List<InputMethodSubtype> subtypes = null;
if (subtypeLocales != null) {
- subtypes = new ArrayList<InputMethodSubtype>();
+ subtypes = new ArrayList<>();
for (String subtypeLocale : subtypeLocales) {
subtypes.add(createDummySubtype(subtypeLocale));
}
@@ -89,7 +88,7 @@
}
private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
- final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+ final List<ImeSubtypeListItem> items = new ArrayList<>();
addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
true /* supportsSwitchingToNextInputMethod*/);
addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
@@ -105,7 +104,7 @@
}
private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
- final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+ final List<ImeSubtypeListItem> items = new ArrayList<>();
addDummyImeSubtypeListItems(items,
"UnknownIme", "UnknownIme",
Arrays.asList("en_US", "hi"),
@@ -121,15 +120,18 @@
}
private void assertNextInputMethod(final ControllerImpl controller,
- final boolean onlyCurrentIme,
- final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) {
+ final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem,
+ final ImeSubtypeListItem nextItem, final ImeSubtypeListItem prevItem) {
InputMethodSubtype subtype = null;
if (currentItem.mSubtypeName != null) {
subtype = createDummySubtype(currentItem.mSubtypeName.toString());
}
final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
- currentItem.mImi, subtype);
+ currentItem.mImi, subtype, true /* forward */);
assertEquals(nextItem, nextIme);
+ final ImeSubtypeListItem prevIme = controller.getNextInputMethod(onlyCurrentIme,
+ currentItem.mImi, subtype, false /* forward */);
+ assertEquals(prevItem, prevIme);
}
private void assertRotationOrder(final ControllerImpl controller,
@@ -138,11 +140,13 @@
final int N = expectedRotationOrderOfImeSubtypeList.length;
for (int i = 0; i < N; i++) {
final int currentIndex = i;
+ final int prevIndex = (currentIndex + N - 1) % N;
final int nextIndex = (currentIndex + 1) % N;
final ImeSubtypeListItem currentItem =
expectedRotationOrderOfImeSubtypeList[currentIndex];
final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
- assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem);
+ final ImeSubtypeListItem prevItem = expectedRotationOrderOfImeSubtypeList[prevIndex];
+ assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem, prevItem);
}
}
@@ -190,29 +194,29 @@
assertRotationOrder(controller, true /* onlyCurrentIme */,
switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- subtypeUnawareIme, null);
+ subtypeUnawareIme, null, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- japaneseIme_ja_JP, null);
+ japaneseIme_ja_JP, null, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- switchUnawareJapaneseIme_ja_JP, null);
+ switchUnawareJapaneseIme_ja_JP, null, null);
// Make sure that disabled IMEs are not accepted.
assertNextInputMethod(controller, false /* onlyCurrentIme */,
- disabledIme_en_US, null);
+ disabledIme_en_US, null, null);
assertNextInputMethod(controller, false /* onlyCurrentIme */,
- disabledIme_hi, null);
+ disabledIme_hi, null, null);
assertNextInputMethod(controller, false /* onlyCurrentIme */,
- disabledSwitchingUnawareIme, null);
+ disabledSwitchingUnawareIme, null, null);
assertNextInputMethod(controller, false /* onlyCurrentIme */,
- disabledSubtypeUnawareIme, null);
+ disabledSubtypeUnawareIme, null, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- disabledIme_en_US, null);
+ disabledIme_en_US, null, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- disabledIme_hi, null);
+ disabledIme_hi, null, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- disabledSwitchingUnawareIme, null);
+ disabledSwitchingUnawareIme, null, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- disabledSubtypeUnawareIme, null);
+ disabledSubtypeUnawareIme, null, null);
}
@SmallTest
@@ -246,7 +250,7 @@
japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
// Check onlyCurrentIme == true.
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- japaneseIme_ja_JP, null);
+ japaneseIme_ja_JP, null, null);
assertRotationOrder(controller, true /* onlyCurrentIme */,
latinIme_fr, latinIme_en_US);
assertRotationOrder(controller, true /* onlyCurrentIme */,
@@ -270,9 +274,9 @@
assertRotationOrder(controller, true /* onlyCurrentIme */,
switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- subtypeUnawareIme, null);
+ subtypeUnawareIme, null, null);
assertNextInputMethod(controller, true /* onlyCurrentIme */,
- switchUnawareJapaneseIme_ja_JP, null);
+ switchUnawareJapaneseIme_ja_JP, null, null);
// Rotation order should be preserved when created with the same subtype list.
final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
@@ -298,7 +302,7 @@
@SmallTest
public void testImeSubtypeListItem() throws Exception {
- final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+ final List<ImeSubtypeListItem> items = new ArrayList<>();
addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
true /* supportsSwitchingToNextInputMethod*/);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index b34af0b..84fc6fe 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -98,7 +98,7 @@
assert(uri == null || uri.getAuthority() == null ||
LauncherActivity.isLaunchUri(uri));
refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
- } else if (intent.getAction() == Intent.ACTION_VIEW) {
+ } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
assert(uri != null);
new OpenUriForViewTask(this).executeOnExecutor(
ProviderExecutor.forAuthority(uri.getAuthority()), uri);
@@ -276,18 +276,6 @@
@Override
public void onDocumentPicked(DocumentInfo doc, Model model) {
- if (doc.isContainer()) {
- openContainerDocument(doc);
- } else {
- openDocument(doc, model);
- }
- }
-
- /**
- * Launches an intent to view the specified document.
- */
- private void openDocument(DocumentInfo doc, Model model) {
-
// Anything on downloads goes through the back through downloads manager
// (that's the MANAGE_DOCUMENT bit).
// This is done for two reasons:
@@ -297,7 +285,13 @@
// like origin URL.
// All other files not on downloads, event APKs, would get no benefit from this
// treatment, thusly the "isDownloads" check.
- if (getCurrentRoot().isDownloads()) {
+
+ // Launch MANAGE_DOCUMENTS only for the root level files, so it's not called for
+ // files in archives. Also, if the activity is already browsing a ZIP from downloads,
+ // then skip MANAGE_DOCUMENTS.
+ final boolean isViewing = Intent.ACTION_VIEW.equals(getIntent().getAction());
+ final boolean isInArchive = mState.stack.size() > 1;
+ if (getCurrentRoot().isDownloads() && !isInArchive && !isViewing) {
// First try managing the document; we expect manager to filter
// based on authority, so we don't grant.
final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
@@ -311,6 +305,17 @@
}
}
+ if (doc.isContainer()) {
+ openContainerDocument(doc);
+ } else {
+ openDocument(doc, model);
+ }
+ }
+
+ /**
+ * Launches an intent to view the specified document.
+ */
+ private void openDocument(DocumentInfo doc, Model model) {
Intent intent = new QuickViewIntentBuilder(
getPackageManager(), getResources(), doc, model).build();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 246b95de..329afdd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -33,7 +33,6 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
@@ -118,9 +117,10 @@
synchronized @Nullable LoaderTask getNextTaskOrReleaseBackgroundThread() {
Preconditions.checkState(mBackgroundThread != null);
- final LoaderTask task = mTaskList.findRunningTask();
- if (task != null) {
- return task;
+ for (final LoaderTask task : mTaskList) {
+ if (task.getState() == LoaderTask.STATE_LOADING) {
+ return task;
+ }
}
final Identifier identifier = mDatabase.getUnmappedDocumentsParent(mDevice.deviceId);
@@ -161,8 +161,21 @@
mTaskList.clearCompletedTasks();
}
- synchronized void clearTask(Identifier parentIdentifier) {
- mTaskList.clearTask(parentIdentifier);
+ /**
+ * Cancels the task for |parentIdentifier|.
+ *
+ * Task is removed from the cached list and it will create new task when |parentIdentifier|'s
+ * children are queried next.
+ */
+ void cancelTask(Identifier parentIdentifier) {
+ final LoaderTask task;
+ synchronized (this) {
+ task = mTaskList.findTask(parentIdentifier);
+ }
+ if (task != null) {
+ task.cancel();
+ mTaskList.remove(task);
+ }
}
/**
@@ -205,14 +218,6 @@
return null;
}
- LoaderTask findRunningTask() {
- for (int i = 0; i < size(); i++) {
- if (get(i).getState() == LoaderTask.STATE_LOADING)
- return get(i);
- }
- return null;
- }
-
void clearCompletedTasks() {
int i = 0;
while (i < size()) {
@@ -223,17 +228,6 @@
}
}
}
-
- void clearTask(Identifier parentIdentifier) {
- for (int i = 0; i < size(); i++) {
- final LoaderTask task = get(i);
- if (task.mIdentifier.mDeviceId == parentIdentifier.mDeviceId &&
- task.mIdentifier.mObjectHandle == parentIdentifier.mObjectHandle) {
- remove(i);
- return;
- }
- }
- }
}
/**
@@ -245,6 +239,7 @@
static final int STATE_LOADING = 1;
static final int STATE_COMPLETED = 2;
static final int STATE_ERROR = 3;
+ static final int STATE_CANCELLED = 4;
final MtpManager mManager;
final MtpDatabase mDatabase;
@@ -272,6 +267,7 @@
synchronized void loadObjectHandles() {
assert mState == STATE_START;
+ mPosition = 0;
int parentHandle = mIdentifier.mObjectHandle;
// Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
// getObjectHandles if we would like to obtain children under the root.
@@ -303,12 +299,10 @@
case STATE_ERROR:
throw mError;
}
-
final Cursor cursor =
mDatabase.queryChildDocuments(columnNames, mIdentifier.mDocumentId);
+ cursor.setExtras(extras);
cursor.setNotificationUri(resolver, createUri());
- cursor.respond(extras);
-
return cursor;
}
@@ -374,6 +368,10 @@
}
}
synchronized (this) {
+ // Check if the task is cancelled or not.
+ if (mState != STATE_LOADING) {
+ return;
+ }
try {
mDatabase.getMapper().putChildDocuments(
mIdentifier.mDeviceId,
@@ -403,6 +401,14 @@
}
/**
+ * Cancels the task.
+ */
+ synchronized void cancel() {
+ mDatabase.getMapper().cancelAddingDocuments(mIdentifier.mDocumentId);
+ mState = STATE_CANCELLED;
+ }
+
+ /**
* Returns a state of the task.
*/
int getState() {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index adc71ae..63f18f3 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -363,6 +363,41 @@
}
/**
+ * Cancels adding documents.
+ * @param parentId
+ */
+ void cancelAddingDocuments(@Nullable String parentId) {
+ final String selection;
+ final String[] args;
+ if (parentId != null) {
+ selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
+ args = strings(parentId);
+ } else {
+ selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+ args = EMPTY_ARGS;
+ }
+
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ if (!mInMappingIds.contains(parentId)) {
+ return;
+ }
+ mInMappingIds.remove(parentId);
+ final ContentValues values = new ContentValues();
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+ mDatabase.getSQLiteDatabase().update(
+ TABLE_DOCUMENTS,
+ values,
+ selection + " AND " + COLUMN_ROW_STATE + " = ?",
+ DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_INVALIDATED)));
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
* Queries candidate for each mappingKey, and returns the first cursor that includes a
* candidate.
*
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 50781bf..1823711 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -308,7 +308,7 @@
final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
mDatabase.deleteDocument(documentId);
- getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
+ getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier);
notifyChildDocumentsChange(parentIdentifier.mDocumentId);
if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
// If the parent is storage, the object might be appeared as child of device because
@@ -402,7 +402,7 @@
final String documentId = mDatabase.putNewDocument(
parentId.mDeviceId, parentDocumentId, record.operationsSupported,
infoWithHandle, 0l);
- getDocumentLoader(parentId).clearTask(parentId);
+ getDocumentLoader(parentId).cancelTask(parentId);
notifyChildDocumentsChange(parentDocumentId);
return documentId;
} catch (FileNotFoundException | RuntimeException error) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 45f89e4..60dd7e1 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -21,6 +21,7 @@
import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
@@ -28,6 +29,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeoutException;
@MediumTest
public class DocumentLoaderTest extends AndroidTestCase {
@@ -141,6 +143,33 @@
}
}
+ public void testCancelTask() throws IOException, InterruptedException {
+ setUpDocument(mManager,
+ DocumentLoader.NUM_INITIAL_ENTRIES + DocumentLoader.NUM_LOADING_ENTRIES + 1);
+
+ // Block the first iteration in the background thread.
+ mManager.blockDocument(
+ 0, DocumentLoader.NUM_INITIAL_ENTRIES + 1);
+ setUpLoader();
+ try (final Cursor cursor = mLoader.queryChildDocuments(
+ MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {
+ assertTrue(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
+ }
+ Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS);
+
+ // Clear task while the first iteration is being blocked.
+ mManager.unblockDocument(
+ 0, DocumentLoader.NUM_INITIAL_ENTRIES + 1);
+ mLoader.cancelTask(mParentIdentifier);
+
+ Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS * 2);
+
+ // Check if it's OK to query invalidated task.
+ try (final Cursor cursor = mLoader.queryChildDocuments(
+ MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {
+ }
+ }
+
private void setUpLoader() {
mLoader = new DocumentLoader(
new MtpDeviceRecord(
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9e2442c..f5854f5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -488,5 +488,12 @@
<action android:name="com.android.systemui.action.CLEAR_TUNER" />
</intent-filter>
</receiver>
+
+ <receiver
+ android:name=".statusbar.KeyboardShortcutsReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.SHOW_KEYBOARD_SHORTCUTS" />
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
index 3067714..2045ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
@@ -51,6 +52,12 @@
|| touchY > mContent.getY() + mContent.getHeight();
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDismissButton.setText(R.string.clear_all_notifications_text);
+ }
+
public boolean isButtonVisible() {
return mDismissButton.getAlpha() != 0.0f;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index fff1491..11aaedf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -255,10 +255,7 @@
}
public void toggleKeyboardShortcuts(int deviceId) {
- InputDevice inputDevice = InputManager.getInstance().getInputDevice(deviceId);
- if (inputDevice != null) {
- mKeyCharacterMap = inputDevice.getKeyCharacterMap();
- }
+ retrieveKeyCharacterMap(deviceId);
if (mKeyboardShortcutsDialog == null) {
Recents.getSystemServices().requestKeyboardShortcuts(mContext,
new KeyboardShortcutsReceiver() {
@@ -278,6 +275,35 @@
}
}
+ /**
+ * Retrieves a {@link KeyCharacterMap} and assigns it to mKeyCharacterMap. If the given id is an
+ * existing device, that device's map is used. Otherwise, it checks first all available devices
+ * and if there is a full keyboard it uses that map, otherwise falls back to the Virtual
+ * Keyboard with its default map.
+ */
+ private void retrieveKeyCharacterMap(int deviceId) {
+ final InputManager inputManager = InputManager.getInstance();
+ if (deviceId != -1) {
+ final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (inputDevice != null) {
+ mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+ return;
+ }
+ }
+ final int[] deviceIds = inputManager.getInputDeviceIds();
+ for (int i = 0; i < deviceIds.length; ++i) {
+ final InputDevice inputDevice = inputManager.getInputDevice(deviceIds[i]);
+ // -1 is the Virtual Keyboard, with the default key map. Use that one only as last
+ // resort.
+ if (inputDevice.getId() != -1 && inputDevice.isFullKeyboard()) {
+ mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+ return;
+ }
+ }
+ final InputDevice inputDevice = inputManager.getInputDevice(-1);
+ mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+ }
+
public void dismissKeyboardShortcutsDialog() {
if (mKeyboardShortcutsDialog != null) {
mKeyboardShortcutsDialog.dismiss();
@@ -488,12 +514,6 @@
final int itemsSize = group.getItems().size();
for (int j = 0; j < itemsSize; j++) {
KeyboardShortcutInfo info = group.getItems().get(j);
- if (info.getKeycode() != KeyEvent.KEYCODE_UNKNOWN
- && !KeyCharacterMap.deviceHasKey(info.getKeycode())) {
- // The user can't achieve this shortcut, so skipping.
- Log.w(TAG, "Keyboard Shortcut contains key not on device, skipping.");
- continue;
- }
List<StringOrDrawable> shortcutKeys = getHumanReadableShortcutKeys(info);
if (shortcutKeys == null) {
// Ignore shortcuts we can't display keys for.
@@ -585,9 +605,7 @@
if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
return shortcutKeys;
}
- // TODO: Have a generic map for when we don't have the device's.
- char displayLabel = mKeyCharacterMap == null
- ? 0 : mKeyCharacterMap.getDisplayLabel(info.getKeycode());
+ char displayLabel = mKeyCharacterMap.getDisplayLabel(info.getKeycode());
if (displayLabel != 0) {
displayLabelString = String.valueOf(displayLabel);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
new file mode 100644
index 0000000..5d22faf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 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.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Receiver for the Keyboard Shortcuts Helper.
+ */
+public class KeyboardShortcutsReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
+ final KeyboardShortcuts keyboardShortcuts = new KeyboardShortcuts(context);
+ keyboardShortcuts.toggleKeyboardShortcuts(-1 /* deviceId unknown */);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
new file mode 100644
index 0000000..03b51c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2016 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.statusbar.car;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * A {@link BatteryController} that is specific to the Auto use-case. For Auto, the battery icon
+ * displays the battery status of a device that is connected via bluetooth and not the system's
+ * battery.
+ */
+public class CarBatteryController extends BroadcastReceiver implements BatteryController {
+ private static final String TAG = "CarBatteryController";
+
+ // According to the Bluetooth HFP 1.5 specification, battery levels are indicated by a
+ // value from 1-5, where these values represent the following:
+ // 0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5
+ // As a result, set the level as the average within that range.
+ private static final int BATTERY_LEVEL_EMPTY = 0;
+ private static final int BATTERY_LEVEL_1 = 12;
+ private static final int BATTERY_LEVEL_2 = 28;
+ private static final int BATTERY_LEVEL_3 = 63;
+ private static final int BATTERY_LEVEL_4 = 87;
+ private static final int BATTERY_LEVEL_FULL = 100;
+
+ private static final int INVALID_BATTERY_LEVEL = -1;
+
+ private final Context mContext;
+
+ private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+ private BluetoothHeadsetClient mBluetoothHeadsetClient;
+
+ private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+
+ private int mLevel;
+
+ /**
+ * An interface indicating the container of a View that will display what the information
+ * in the {@link CarBatteryController}.
+ */
+ public interface BatteryViewHandler {
+ void hideBatteryView();
+ void showBatteryView();
+ }
+
+ private BatteryViewHandler mBatteryViewHandler;
+
+ public CarBatteryController(Context context) {
+ mContext = context;
+
+ mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener,
+ BluetoothProfile.HEADSET_CLIENT);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CarBatteryController state:");
+ pw.print(" mLevel=");
+ pw.println(mLevel);
+ }
+
+ @Override
+ public void setPowerSaveMode(boolean powerSave) {
+ // No-op. No power save mode for the car.
+ }
+
+ @Override
+ public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+ mChangeCallbacks.add(cb);
+
+ // There is no way to know if the phone is plugged in or charging via bluetooth, so pass
+ // false for these values.
+ cb.onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
+ cb.onPowerSaveChanged(false /* isPowerSave */);
+ }
+
+ @Override
+ public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+ mChangeCallbacks.remove(cb);
+ }
+
+ public void addBatteryViewHandler(BatteryViewHandler batteryViewHandler) {
+ mBatteryViewHandler = batteryViewHandler;
+ }
+
+ public void startListening() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ mContext.registerReceiver(this, filter);
+ }
+
+ public void stopListening() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onReceive(). action: " + action);
+ }
+
+ if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Received ACTION_AG_EVENT");
+ }
+
+ int batteryLevel = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
+ INVALID_BATTERY_LEVEL);
+
+ updateBatteryLevel(batteryLevel);
+
+ if (batteryLevel != INVALID_BATTERY_LEVEL && mBatteryViewHandler != null) {
+ mBatteryViewHandler.showBatteryView();
+ }
+ } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+ int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+ Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: "
+ + oldState + " -> " + newState);
+
+ }
+ BluetoothDevice device =
+ (BluetoothDevice)intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
+ updateBatteryIcon(device, newState);
+ }
+ }
+
+ /**
+ * Converts the battery level to a percentage that can be displayed on-screen and notifies
+ * any {@link BatteryStateChangeCallback}s of this.
+ */
+ private void updateBatteryLevel(int batteryLevel) {
+ if (batteryLevel == INVALID_BATTERY_LEVEL) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Battery level invalid. Ignoring.");
+ }
+ return;
+ }
+
+ // The battery level is a value between 0-5. Let the default battery level be 0.
+ switch (batteryLevel) {
+ case 5:
+ mLevel = BATTERY_LEVEL_FULL;
+ break;
+ case 4:
+ mLevel = BATTERY_LEVEL_4;
+ break;
+ case 3:
+ mLevel = BATTERY_LEVEL_3;
+ break;
+ case 2:
+ mLevel = BATTERY_LEVEL_2;
+ break;
+ case 1:
+ mLevel = BATTERY_LEVEL_1;
+ break;
+ case 0:
+ default:
+ mLevel = BATTERY_LEVEL_EMPTY;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Battery level: " + batteryLevel + "; setting mLevel as: " + mLevel);
+ }
+
+ notifyBatteryLevelChanged();
+ }
+
+ /**
+ * Updates the display of the battery icon depending on the given connection state from the
+ * given {@link BluetoothDevice}.
+ */
+ private void updateBatteryIcon(BluetoothDevice device, int newState) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Device connected");
+ }
+
+ if (mBatteryViewHandler != null) {
+ mBatteryViewHandler.showBatteryView();
+ }
+
+ if (mBluetoothHeadsetClient == null || device == null) {
+ return;
+ }
+
+ // Check if battery information is available and immediately update.
+ Bundle featuresBundle = mBluetoothHeadsetClient.getCurrentAgEvents(device);
+ if (featuresBundle == null) {
+ return;
+ }
+
+ int batteryLevel = featuresBundle.getInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
+ INVALID_BATTERY_LEVEL);
+ updateBatteryLevel(batteryLevel);
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Device disconnected");
+ }
+
+ if (mBatteryViewHandler != null) {
+ mBatteryViewHandler.hideBatteryView();
+ }
+ }
+ }
+
+ @Override
+ public boolean isPowerSave() {
+ // Power save is not valid for the car, so always return false.
+ return false;
+ }
+
+ private void notifyBatteryLevelChanged() {
+ for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) {
+ mChangeCallbacks.get(i)
+ .onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
+ }
+ }
+
+ private final ServiceListener mHfpServiceListener = new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (profile == BluetoothProfile.HEADSET_CLIENT) {
+ mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (profile == BluetoothProfile.HEADSET_CLIENT) {
+ mBluetoothHeadsetClient = null;
+ }
+ }
+ };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 4add3cb..811687c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -22,37 +22,75 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewStub;
import android.view.WindowManager;
-
+import com.android.systemui.BatteryMeterView;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.PhoneStatusBarView;
+import com.android.systemui.statusbar.policy.BatteryController;
/**
* A status bar (and navigation bar) tailored for the automotive use case.
*/
-public class CarStatusBar extends PhoneStatusBar {
+public class CarStatusBar extends PhoneStatusBar implements
+ CarBatteryController.BatteryViewHandler {
+ private static final String TAG = "CarStatusBar";
+
private TaskStackListenerImpl mTaskStackListener;
private CarNavigationBarView mCarNavigationBar;
private CarNavigationBarController mController;
private FullscreenUserSwitcher mFullscreenUserSwitcher;
+ private CarBatteryController mCarBatteryController;
+ private BatteryMeterView mBatteryMeterView;
+
@Override
public void start() {
super.start();
mTaskStackListener = new TaskStackListenerImpl();
SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
registerPackageChangeReceivers();
+
+ mCarBatteryController.startListening();
+ }
+
+ @Override
+ public void destroy() {
+ mCarBatteryController.stopListening();
+ super.destroy();
+ }
+
+ @Override
+ protected PhoneStatusBarView makeStatusBarView() {
+ PhoneStatusBarView statusBarView = super.makeStatusBarView();
+
+ mBatteryMeterView = ((BatteryMeterView) statusBarView.findViewById(R.id.battery));
+
+ // By default, the BatteryMeterView should not be visible. It will be toggled visible
+ // when a device has connected by bluetooth.
+ mBatteryMeterView.setVisibility(View.GONE);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
+ }
+
+ return statusBarView;
+ }
+
+ @Override
+ protected BatteryController createBatteryController() {
+ mCarBatteryController = new CarBatteryController(mContext);
+ mCarBatteryController.addBatteryViewHandler(this);
+ return mCarBatteryController;
}
@Override
@@ -85,6 +123,28 @@
}
+ @Override
+ public void showBatteryView() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
+ }
+
+ if (mBatteryMeterView != null) {
+ mBatteryMeterView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void hideBatteryView() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
+ }
+
+ if (mBatteryMeterView != null) {
+ mBatteryMeterView.setVisibility(View.GONE);
+ }
+ }
+
private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index a27ec28..fffb20a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -26,6 +26,7 @@
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Objects;
/**
@@ -37,6 +38,7 @@
private OnGroupChangeListener mListener;
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
+ private HeadsUpManager mHeadsUpManager;
public void setOnGroupChangeListener(OnGroupChangeListener listener) {
mListener = listener;
@@ -142,6 +144,9 @@
&& group.summary.notification.getNotification().isGroupSummary()
&& hasIsolatedChildren(group)));
if (prevSuppressed != group.suppressed) {
+ if (group.suppressed) {
+ handleSuppressedSummaryHeadsUpped(group.summary);
+ }
mListener.onGroupsChanged();
}
}
@@ -160,6 +165,15 @@
return count;
}
+ private NotificationData.Entry getIsolatedChild(String groupKey) {
+ for (StatusBarNotification sbn : mIsolatedEntries.values()) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ return mGroupMap.get(sbn.getKey()).summary;
+ }
+ }
+ return null;
+ }
+
public void onEntryUpdated(NotificationData.Entry entry,
StatusBarNotification oldNotification) {
if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
@@ -332,6 +346,9 @@
// it doesn't lead to an update.
updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
mListener.onGroupsChanged();
+ } else {
+ handleSuppressedSummaryHeadsUpped(entry);
+
}
} else {
if (mIsolatedEntries.containsKey(sbn.getKey())) {
@@ -344,6 +361,32 @@
}
}
+ private void handleSuppressedSummaryHeadsUpped(NotificationData.Entry entry) {
+ StatusBarNotification sbn = entry.notification;
+ if (!isGroupSuppressed(sbn.getGroupKey())
+ || !sbn.getNotification().isGroupSummary()
+ || !entry.row.isHeadsUp()) {
+ return;
+ }
+ // The parent of a suppressed group got huned, lets hun the child!
+ NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
+ if (notificationGroup != null) {
+ Iterator<NotificationData.Entry> iterator = notificationGroup.children.iterator();
+ NotificationData.Entry child = iterator.hasNext() ? iterator.next() : null;
+ if (child == null) {
+ child = getIsolatedChild(sbn.getGroupKey());
+ }
+ if (child != null) {
+ if (mHeadsUpManager.isHeadsUp(child.key)) {
+ mHeadsUpManager.updateNotification(child, true);
+ } else {
+ mHeadsUpManager.showNotification(child);
+ }
+ }
+ }
+ mHeadsUpManager.releaseImmediately(entry.key);
+ }
+
private boolean shouldIsolate(StatusBarNotification sbn) {
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
return (sbn.isGroup() && !sbn.getNotification().isGroupSummary())
@@ -360,6 +403,10 @@
|| notificationGroup.summary.row.getTranslationY() < 0;
}
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
public static class NotificationGroup {
public final HashSet<NotificationData.Entry> children = new HashSet<>();
public NotificationData.Entry summary;
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 933d5bd..e52a401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -21,9 +21,7 @@
import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -147,6 +145,7 @@
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+import com.android.systemui.statusbar.policy.BatteryControllerImpl;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
@@ -730,6 +729,7 @@
mHeadsUpManager.addListener(mGroupManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
mNotificationData.setHeadsUpManager(mHeadsUpManager);
+ mGroupManager.setHeadsUpManager(mHeadsUpManager);
if (MULTIUSER_DEBUG) {
mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
@@ -826,7 +826,7 @@
// Other icons
mLocationController = new LocationControllerImpl(mContext,
mHandlerThread.getLooper()); // will post a notification
- mBatteryController = new BatteryController(mContext);
+ mBatteryController = createBatteryController();
mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
@@ -943,6 +943,10 @@
return mStatusBarView;
}
+ protected BatteryController createBatteryController() {
+ return new BatteryControllerImpl(mContext);
+ }
+
@Override
protected void reInflateViews() {
super.reInflateViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index bb3e116..ea64fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -16,158 +16,33 @@
package com.android.systemui.statusbar.policy;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.util.Log;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-public class BatteryController extends BroadcastReceiver {
- private static final String TAG = "BatteryController";
+public interface BatteryController {
+ /**
+ * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
+ */
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
- public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+ /**
+ * Sets if the current device is in power save mode.
+ */
+ void setPowerSaveMode(boolean powerSave);
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /**
+ * Returns {@code true} if the device is currently in power save mode.
+ */
+ boolean isPowerSave();
- private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
- private final PowerManager mPowerManager;
- private final Handler mHandler;
+ void addStateChangedCallback(BatteryStateChangeCallback cb);
+ void removeStateChangedCallback(BatteryStateChangeCallback cb);
- private int mLevel;
- private boolean mPluggedIn;
- private boolean mCharging;
- private boolean mCharged;
- private boolean mPowerSave;
- private boolean mTestmode = false;
-
- public BatteryController(Context context) {
- mHandler = new Handler();
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
- filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
- filter.addAction(ACTION_LEVEL_TEST);
- context.registerReceiver(this, filter);
-
- updatePowerSave();
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("BatteryController state:");
- pw.print(" mLevel="); pw.println(mLevel);
- pw.print(" mPluggedIn="); pw.println(mPluggedIn);
- pw.print(" mCharging="); pw.println(mCharging);
- pw.print(" mCharged="); pw.println(mCharged);
- pw.print(" mPowerSave="); pw.println(mPowerSave);
- }
-
- public void setPowerSaveMode(boolean powerSave) {
- mPowerManager.setPowerSaveMode(powerSave);
- }
-
- public void addStateChangedCallback(BatteryStateChangeCallback cb) {
- mChangeCallbacks.add(cb);
- cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
- cb.onPowerSaveChanged(mPowerSave);
- }
-
- public void removeStateChangedCallback(BatteryStateChangeCallback cb) {
- mChangeCallbacks.remove(cb);
- }
-
- public void onReceive(final Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
- if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
- mLevel = (int)(100f
- * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
- / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
- mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
-
- final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_UNKNOWN);
- mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
- mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
-
- fireBatteryLevelChanged();
- } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
- updatePowerSave();
- } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
- setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
- } else if (action.equals(ACTION_LEVEL_TEST)) {
- mTestmode = true;
- mHandler.post(new Runnable() {
- int curLevel = 0;
- int incr = 1;
- int saveLevel = mLevel;
- boolean savePlugged = mPluggedIn;
- Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
- @Override
- public void run() {
- if (curLevel < 0) {
- mTestmode = false;
- dummy.putExtra("level", saveLevel);
- dummy.putExtra("plugged", savePlugged);
- dummy.putExtra("testmode", false);
- } else {
- dummy.putExtra("level", curLevel);
- dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
- : 0);
- dummy.putExtra("testmode", true);
- }
- context.sendBroadcast(dummy);
-
- if (!mTestmode) return;
-
- curLevel += incr;
- if (curLevel == 100) {
- incr *= -1;
- }
- mHandler.postDelayed(this, 200);
- }
- });
- }
- }
-
- public boolean isPowerSave() {
- return mPowerSave;
- }
-
- private void updatePowerSave() {
- setPowerSave(mPowerManager.isPowerSaveMode());
- }
-
- private void setPowerSave(boolean powerSave) {
- if (powerSave == mPowerSave) return;
- mPowerSave = powerSave;
- if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
- firePowerSaveChanged();
- }
-
- private void fireBatteryLevelChanged() {
- final int N = mChangeCallbacks.size();
- for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
- }
- }
-
- private void firePowerSaveChanged() {
- final int N = mChangeCallbacks.size();
- for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
- }
- }
-
- public interface BatteryStateChangeCallback {
+ /**
+ * A listener that will be notified whenever a change in battery level or power save mode
+ * has occurred.
+ */
+ interface BatteryStateChangeCallback {
void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
void onPowerSaveChanged(boolean isPowerSave);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
new file mode 100644
index 0000000..24207f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 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.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Default implementation of a {@link BatteryController}. This controller monitors for battery
+ * level change events that are broadcasted by the system.
+ */
+public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
+ private static final String TAG = "BatteryController";
+
+ public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+ private final PowerManager mPowerManager;
+ private final Handler mHandler;
+
+ protected int mLevel;
+ protected boolean mPluggedIn;
+ protected boolean mCharging;
+ protected boolean mCharged;
+ protected boolean mPowerSave;
+ private boolean mTestmode = false;
+
+ public BatteryControllerImpl(Context context) {
+ mHandler = new Handler();
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
+ filter.addAction(ACTION_LEVEL_TEST);
+ context.registerReceiver(this, filter);
+
+ updatePowerSave();
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("BatteryController state:");
+ pw.print(" mLevel="); pw.println(mLevel);
+ pw.print(" mPluggedIn="); pw.println(mPluggedIn);
+ pw.print(" mCharging="); pw.println(mCharging);
+ pw.print(" mCharged="); pw.println(mCharged);
+ pw.print(" mPowerSave="); pw.println(mPowerSave);
+ }
+
+ @Override
+ public void setPowerSaveMode(boolean powerSave) {
+ mPowerManager.setPowerSaveMode(powerSave);
+ }
+
+ @Override
+ public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+ mChangeCallbacks.add(cb);
+ cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+ cb.onPowerSaveChanged(mPowerSave);
+ }
+
+ @Override
+ public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+ mChangeCallbacks.remove(cb);
+ }
+
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
+ mLevel = (int)(100f
+ * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
+ / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+ mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
+
+ final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
+ mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
+
+ fireBatteryLevelChanged();
+ } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
+ updatePowerSave();
+ } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
+ setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
+ } else if (action.equals(ACTION_LEVEL_TEST)) {
+ mTestmode = true;
+ mHandler.post(new Runnable() {
+ int curLevel = 0;
+ int incr = 1;
+ int saveLevel = mLevel;
+ boolean savePlugged = mPluggedIn;
+ Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ @Override
+ public void run() {
+ if (curLevel < 0) {
+ mTestmode = false;
+ dummy.putExtra("level", saveLevel);
+ dummy.putExtra("plugged", savePlugged);
+ dummy.putExtra("testmode", false);
+ } else {
+ dummy.putExtra("level", curLevel);
+ dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
+ : 0);
+ dummy.putExtra("testmode", true);
+ }
+ context.sendBroadcast(dummy);
+
+ if (!mTestmode) return;
+
+ curLevel += incr;
+ if (curLevel == 100) {
+ incr *= -1;
+ }
+ mHandler.postDelayed(this, 200);
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean isPowerSave() {
+ return mPowerSave;
+ }
+
+ private void updatePowerSave() {
+ setPowerSave(mPowerManager.isPowerSaveMode());
+ }
+
+ private void setPowerSave(boolean powerSave) {
+ if (powerSave == mPowerSave) return;
+ mPowerSave = powerSave;
+ if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
+ firePowerSaveChanged();
+ }
+
+ protected void fireBatteryLevelChanged() {
+ final int N = mChangeCallbacks.size();
+ for (int i = 0; i < N; i++) {
+ mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+ }
+ }
+
+ private void firePowerSaveChanged() {
+ final int N = mChangeCallbacks.size();
+ for (int i = 0; i < N; i++) {
+ mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
+ }
+ }
+}
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 ab81712..ebefdde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -185,6 +185,11 @@
if (alert) {
HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
+ if (headsUpEntry == null) {
+ // the entry was released before this update (i.e by a listener) This can happen
+ // with the groupmanager
+ return;
+ }
headsUpEntry.updateEntry();
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index ac7872a..58e3674 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -2546,7 +2546,8 @@
return false;
}
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+ onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
+ true /* forward */);
if (nextSubtype == null) {
return false;
}
@@ -2569,7 +2570,8 @@
return false;
}
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+ false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
+ true /* forward */);
if (nextSubtype == null) {
return false;
}
@@ -2963,9 +2965,8 @@
private void handleSwitchInputMethod(final boolean forwardDirection) {
synchronized (mMethodMap) {
- // TODO: Support forwardDirection.
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- false, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+ false, mMethodMap.get(mCurMethodId), mCurrentSubtype, forwardDirection);
if (nextSubtype == null) {
return;
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index e32d1d1..e69c662 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1447,7 +1447,8 @@
if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
- mOverrideConfig = calculateOverrideConfig(mTmpRect, insetBounds);
+ mOverrideConfig = calculateOverrideConfig(mTmpRect, insetBounds,
+ mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
if (mFullscreen != oldFullscreen) {
@@ -1457,33 +1458,38 @@
return !mOverrideConfig.equals(oldConfig) ? mOverrideConfig : null;
}
- private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds) {
+ private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds,
+ boolean overrideWidth, boolean overrideHeight) {
mTmpRect2.set(inInsetBounds);
mService.mWindowManager.subtractNonDecorInsets(mTmpRect2);
int leftInset = mTmpRect2.left - inInsetBounds.left;
int topInset = mTmpRect2.top - inInsetBounds.top;
- int rightInset = inInsetBounds.right - mTmpRect2.right;
- int bottomInset = inInsetBounds.bottom - mTmpRect2.bottom;
+ int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
+ int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
}
- private void subtractStableInsets(Rect inOutBounds, Rect inInsetBounds) {
+ private void subtractStableInsets(Rect inOutBounds, Rect inInsetBounds,
+ boolean overrideWidth, boolean overrideHeight) {
mTmpRect2.set(inInsetBounds);
mService.mWindowManager.subtractStableInsets(mTmpRect2);
int leftInset = mTmpRect2.left - inInsetBounds.left;
int topInset = mTmpRect2.top - inInsetBounds.top;
- int rightInset = inInsetBounds.right - mTmpRect2.right;
- int bottomInset = inInsetBounds.bottom - mTmpRect2.bottom;
+ int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
+ int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
}
- private Configuration calculateOverrideConfig(Rect bounds, Rect insetBounds) {
+ private Configuration calculateOverrideConfig(Rect bounds, Rect insetBounds,
+ boolean overrideWidth, boolean overrideHeight) {
mTmpNonDecorBounds.set(bounds);
mTmpStableBounds.set(bounds);
subtractNonDecorInsets(
- mTmpNonDecorBounds, insetBounds != null ? insetBounds : bounds);
+ mTmpNonDecorBounds, insetBounds != null ? insetBounds : bounds,
+ overrideWidth, overrideHeight);
subtractStableInsets(
- mTmpStableBounds, insetBounds != null ? insetBounds : bounds);
+ mTmpStableBounds, insetBounds != null ? insetBounds : bounds,
+ overrideWidth, overrideHeight);
// For calculating screenWidthDp, screenWidthDp, we use the stable inset screen area,
// i.e. the screen area without the system bars.
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 7e25632..a11ee74 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -28,6 +28,8 @@
import dalvik.system.VMRuntime;
+import java.util.Arrays;
+
public final class Installer extends SystemService {
private static final String TAG = "Installer";
@@ -90,14 +92,14 @@
mInstaller.execute("migrate_app_data", uuid, pkgname, userid, flags);
}
- public void clearAppData(String uuid, String pkgname, int userid, int flags)
+ public void clearAppData(String uuid, String pkgname, int userid, int flags, long ceDataInode)
throws InstallerException {
- mInstaller.execute("clear_app_data", uuid, pkgname, userid, flags);
+ mInstaller.execute("clear_app_data", uuid, pkgname, userid, flags, ceDataInode);
}
- public void destroyAppData(String uuid, String pkgname, int userid, int flags)
+ public void destroyAppData(String uuid, String pkgname, int userid, int flags, long ceDataInode)
throws InstallerException {
- mInstaller.execute("destroy_app_data", uuid, pkgname, userid, flags);
+ mInstaller.execute("destroy_app_data", uuid, pkgname, userid, flags, ceDataInode);
}
public void moveCompleteApp(String from_uuid, String to_uuid, String package_name,
@@ -107,31 +109,26 @@
data_app_name, appid, seinfo, targetSdkVersion);
}
- public void getAppSize(String uuid, String pkgname, int userid, int flags, String apkPath,
- String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets,
- PackageStats pStats) throws InstallerException {
- for (String instructionSet : instructionSets) {
- assertValidInstructionSet(instructionSet);
- }
-
- // TODO: Extend getSizeInfo to look at the full subdirectory tree,
- // not just the first level.
- // TODO: Extend getSizeInfo to look at *all* instrution sets, not
- // just the primary.
- final String rawRes = mInstaller.executeForResult("get_app_size", uuid, pkgname, userid,
- flags, apkPath, libDirPath, fwdLockApkPath, asecPath, instructionSets[0]);
- final String res[] = rawRes.split(" ");
-
- if ((res == null) || (res.length != 5)) {
- throw new InstallerException("Invalid size result: " + rawRes);
- }
+ public void getAppSize(String uuid, String pkgname, int userid, int flags, long ceDataInode,
+ String codePath, PackageStats stats) throws InstallerException {
+ final String[] res = mInstaller.execute("get_app_size", uuid, pkgname, userid, flags,
+ ceDataInode, codePath);
try {
- pStats.codeSize = Long.parseLong(res[1]);
- pStats.dataSize = Long.parseLong(res[2]);
- pStats.cacheSize = Long.parseLong(res[3]);
- pStats.externalCodeSize = Long.parseLong(res[4]);
- } catch (NumberFormatException e) {
- throw new InstallerException("Invalid size result: " + rawRes);
+ stats.codeSize += Long.parseLong(res[1]);
+ stats.dataSize += Long.parseLong(res[2]);
+ stats.cacheSize += Long.parseLong(res[3]);
+ } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+ throw new InstallerException("Invalid size result: " + Arrays.toString(res));
+ }
+ }
+
+ public long getAppDataInode(String uuid, String pkgname, int userid, int flags)
+ throws InstallerException {
+ final String[] res = mInstaller.execute("get_app_data_inode", uuid, pkgname, userid, flags);
+ try {
+ return Long.parseLong(res[1]);
+ } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+ throw new InstallerException("Invalid inode result: " + Arrays.toString(res));
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bee276e..ddccbb3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2388,9 +2388,9 @@
if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
psit.remove();
logCriticalInfo(Log.WARN, "System package " + ps.name
- + " no longer exists; wiping its data");
- // No apps are running this early, so no need to freeze
- removeDataDirsLIF(null, ps.name);
+ + " no longer exists; it's data will be wiped");
+ // Actual deletion of code and data will be handled by later
+ // reconciliation step
} else {
final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
@@ -2403,8 +2403,13 @@
//look for any incomplete package installations
ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();
for (int i = 0; i < deletePkgsList.size(); i++) {
- // No apps are running this early, so no need to freeze
- cleanupInstallFailedPackageLIF(deletePkgsList.get(i));
+ // Actual deletion of code and data will be handled by later
+ // reconciliation step
+ final String packageName = deletePkgsList.get(i).name;
+ logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + packageName);
+ synchronized (mPackages) {
+ mSettings.removePackageLPw(packageName);
+ }
}
//delete tmp files
@@ -2437,9 +2442,9 @@
String msg;
if (deletedPkg == null) {
msg = "Updated system package " + deletedAppName
- + " no longer exists; wiping its data";
- // No apps are running this early, so no need to freeze
- removeDataDirsLIF(null, deletedAppName);
+ + " no longer exists; it's data will be wiped";
+ // Actual deletion of code and data will be handled by later
+ // reconciliation step
} else {
msg = "Updated system app + " + deletedAppName
+ " no longer present; removing system privileges for "
@@ -2585,7 +2590,7 @@
} else {
storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
}
- reconcileAppsData(StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.USER_SYSTEM,
+ reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.USER_SYSTEM,
storageFlags);
// If this is first boot after an OTA, and a normal boot, then
@@ -2596,8 +2601,11 @@
final PackageSetting ps = mSettings.mPackages.valueAt(i);
if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) {
// No apps are running this early, so no need to freeze
- deleteCodeCacheDirsLIF(ps.volumeUuid, ps.name);
+ clearAppDataLIF(ps.pkg, UserHandle.USER_ALL,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
+ clearAppProfilesLIF(ps.pkg);
}
ver.fingerprint = Build.FINGERPRINT;
}
@@ -2981,24 +2989,6 @@
}
}
- void cleanupInstallFailedPackageLIF(PackageSetting ps) {
- logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + ps.name);
-
- removeDataDirsLIF(ps.volumeUuid, ps.name);
- if (ps.codePath != null) {
- removeCodePathLI(ps.codePath);
- }
- if (ps.resourcePath != null && !ps.resourcePath.equals(ps.codePath)) {
- if (ps.resourcePath.isDirectory()) {
- FileUtils.deleteContents(ps.resourcePath);
- }
- ps.resourcePath.delete();
- }
- synchronized (mPackages) {
- mSettings.removePackageLPw(ps.name);
- }
- }
-
static int[] appendInts(int[] cur, int[] add) {
if (add == null) return cur;
if (cur == null) return add;
@@ -7321,23 +7311,6 @@
return true;
}
- private boolean removeDataDirsLIF(String volumeUuid, String packageName) {
- // TODO: triage flags as part of 26466827
- final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
-
- boolean res = true;
- final int[] userIds = sUserManager.getUserIds();
- for (int userId : userIds) {
- try {
- mInstaller.destroyAppData(volumeUuid, packageName, userId, flags);
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to delete data directory", e);
- res = false;
- }
- }
- return res;
- }
-
void removeCodePathLI(File codePath) {
if (codePath.isDirectory()) {
try {
@@ -7350,77 +7323,106 @@
}
}
- void destroyAppDataLI(String volumeUuid, String packageName, int userId, int flags) {
- try (PackageFreezer freezer = freezePackage(packageName, "destroyAppDataLI")) {
- try {
- mInstaller.destroyAppData(volumeUuid, packageName, userId, flags);
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to destroy app data", e);
- }
- }
+ private int[] resolveUserIds(int userId) {
+ return (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds() : new int[] { userId };
}
- void restoreconAppDataLI(String volumeUuid, String packageName, int userId, int flags,
- int appId, String seinfo) {
- try {
- mInstaller.restoreconAppData(volumeUuid, packageName, userId, flags, appId, seinfo);
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to restorecon for " + packageName + ": " + e);
- }
- }
-
- private void deleteProfilesLIF(PackageParser.Package pkg, boolean destroy) {
- try {
- if (destroy) {
- mInstaller.destroyAppProfiles(pkg.packageName);
- } else {
- mInstaller.clearAppProfiles(pkg.packageName);
- }
- } catch (InstallerException ex) {
- Log.e(TAG, "Could not delete profiles for package " + pkg.packageName);
- }
- }
-
- private void deleteCodeCacheDirsLIF(String volumeUuid, String packageName) {
- final PackageParser.Package pkg;
- synchronized (mPackages) {
- pkg = mPackages.get(packageName);
- }
+ private void clearAppDataLIF(PackageParser.Package pkg, int userId, int flags) {
if (pkg == null) {
- Slog.w(TAG, "Failed to delete code cache directory. No package: " + packageName);
+ Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
- deleteCodeCacheDirsLIF(pkg);
+ clearAppDataLeafLIF(pkg, userId, flags);
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ clearAppDataLeafLIF(pkg.childPackages.get(i), userId, flags);
+ }
}
- private void deleteCodeCacheDirsLIF(PackageParser.Package pkg) {
- // TODO: triage flags as part of 26466827
- final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
-
- int[] users = sUserManager.getUserIds();
- int res = 0;
- for (int user : users) {
- // Remove the parent code cache
+ private void clearAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
+ final PackageSetting ps;
+ synchronized (mPackages) {
+ ps = mSettings.mPackages.get(pkg.packageName);
+ }
+ for (int realUserId : resolveUserIds(userId)) {
+ final long ceDataInode = (ps != null) ? ps.getCeDataInode(realUserId) : 0;
try {
- mInstaller.clearAppData(pkg.volumeUuid, pkg.packageName, user,
- flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ mInstaller.clearAppData(pkg.volumeUuid, pkg.packageName, realUserId, flags,
+ ceDataInode);
} catch (InstallerException e) {
- Slog.w(TAG, "Failed to delete code cache directory", e);
+ Slog.w(TAG, String.valueOf(e));
}
- final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- PackageParser.Package childPkg = pkg.childPackages.get(i);
- // Remove the child code cache
- try {
- mInstaller.clearAppData(childPkg.volumeUuid, childPkg.packageName,
- user, flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to delete code cache directory", e);
- }
+ }
+ }
+
+ private void destroyAppDataLIF(PackageParser.Package pkg, int userId, int flags) {
+ if (pkg == null) {
+ Slog.wtf(TAG, "Package was null!", new Throwable());
+ return;
+ }
+ destroyAppDataLeafLIF(pkg, userId, flags);
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ destroyAppDataLeafLIF(pkg.childPackages.get(i), userId, flags);
+ }
+ }
+
+ private void destroyAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
+ final PackageSetting ps;
+ synchronized (mPackages) {
+ ps = mSettings.mPackages.get(pkg.packageName);
+ }
+ for (int realUserId : resolveUserIds(userId)) {
+ final long ceDataInode = (ps != null) ? ps.getCeDataInode(realUserId) : 0;
+ try {
+ mInstaller.destroyAppData(pkg.volumeUuid, pkg.packageName, realUserId, flags,
+ ceDataInode);
+ } catch (InstallerException e) {
+ Slog.w(TAG, String.valueOf(e));
}
}
}
+ private void destroyAppProfilesLIF(PackageParser.Package pkg) {
+ if (pkg == null) {
+ Slog.wtf(TAG, "Package was null!", new Throwable());
+ return;
+ }
+ destroyAppProfilesLeafLIF(pkg);
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ destroyAppProfilesLeafLIF(pkg.childPackages.get(i));
+ }
+ }
+
+ private void destroyAppProfilesLeafLIF(PackageParser.Package pkg) {
+ try {
+ mInstaller.destroyAppProfiles(pkg.packageName);
+ } catch (InstallerException e) {
+ Slog.w(TAG, String.valueOf(e));
+ }
+ }
+
+ private void clearAppProfilesLIF(PackageParser.Package pkg) {
+ if (pkg == null) {
+ Slog.wtf(TAG, "Package was null!", new Throwable());
+ return;
+ }
+ clearAppProfilesLeafLIF(pkg);
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ clearAppProfilesLeafLIF(pkg.childPackages.get(i));
+ }
+ }
+
+ private void clearAppProfilesLeafLIF(PackageParser.Package pkg) {
+ try {
+ mInstaller.clearAppProfiles(pkg.packageName);
+ } catch (InstallerException e) {
+ Slog.w(TAG, String.valueOf(e));
+ }
+ }
+
private void setInstallAndUpdateTime(PackageParser.Package pkg, long firstInstallTime,
long lastUpdateTime) {
// Set parent install/update time
@@ -7607,7 +7609,9 @@
} finally {
if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
// DELETE_DATA_ON_FAILURES is only used by frozen paths
- removeDataDirsLIF(pkg.volumeUuid, pkg.packageName);
+ destroyAppDataLIF(pkg, UserHandle.USER_ALL,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+ destroyAppProfilesLIF(pkg);
}
}
}
@@ -11239,7 +11243,10 @@
if (installed) {
if (pkgSetting.pkg != null) {
- prepareAppDataAfterInstall(pkgSetting.pkg);
+ synchronized (mInstallLock) {
+ // We don't need to freeze for a brand new install
+ prepareAppDataAfterInstallLIF(pkgSetting.pkg);
+ }
}
sendPackageAddedForUser(packageName, pkgSetting, userId);
}
@@ -13365,7 +13372,12 @@
synchronized (mInstallLock) {
// Clean up both app data and code
// All package moves are frozen until finished
- removeDataDirsLIF(volumeUuid, move.packageName);
+ try {
+ mInstaller.destroyAppData(volumeUuid, move.packageName, UserHandle.USER_ALL,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE, 0);
+ } catch (InstallerException e) {
+ Slog.w(TAG, String.valueOf(e));
+ }
removeCodePathLI(codeFile);
}
return true;
@@ -13546,7 +13558,7 @@
updateSettingsLI(newPackage, installerPackageName, null, res, user);
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- prepareAppDataAfterInstall(newPackage);
+ prepareAppDataAfterInstallLIF(newPackage);
} else {
// Remove package from internal structures, but keep around any
@@ -13732,8 +13744,9 @@
sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null);
}
- deleteCodeCacheDirsLIF(pkg);
- deleteProfilesLIF(pkg, /*destroy*/ false);
+ clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
+ | StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ clearAppProfilesLIF(pkg);
try {
final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags,
@@ -13760,7 +13773,7 @@
childPs.oldCodePaths = ps.oldCodePaths;
}
}
- prepareAppDataAfterInstall(newPackage);
+ prepareAppDataAfterInstallLIF(newPackage);
addedPkg = true;
} catch (PackageManagerException e) {
res.setError("Package couldn't be installed in " + pkg.codePath, e);
@@ -13864,8 +13877,9 @@
}
// Successfully disabled the old package. Now proceed with re-installation
- deleteCodeCacheDirsLIF(pkg);
- deleteProfilesLIF(pkg, /*destroy*/ false);
+ clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
+ | StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ clearAppProfilesLIF(pkg);
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
pkg.setApplicationInfoFlags(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP,
@@ -13921,7 +13935,7 @@
}
updateSettingsLI(newPackage, installerPackageName, allUsers, res, user);
- prepareAppDataAfterInstall(newPackage);
+ prepareAppDataAfterInstallLIF(newPackage);
}
} catch (PackageManagerException e) {
res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR);
@@ -15074,11 +15088,12 @@
PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
String packageName = ps.name;
if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
- removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
// Retrieve object to delete permissions for shared user later on
+ final PackageParser.Package deletedPkg;
final PackageSetting deletedPs;
// reader
synchronized (mPackages) {
+ deletedPkg = mPackages.get(packageName);
deletedPs = mSettings.mPackages.get(packageName);
if (outInfo != null) {
outInfo.removedPackage = packageName;
@@ -15087,13 +15102,19 @@
: null;
}
}
- if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
- removeDataDirsLIF(ps.volumeUuid, ps.name);
+
+ removePackageLI(ps, (flags & REMOVE_CHATTY) != 0);
+
+ if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+ destroyAppDataLIF(deletedPkg, UserHandle.USER_ALL,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+ destroyAppProfilesLIF(deletedPkg);
if (outInfo != null) {
outInfo.dataRemoved = true;
}
schedulePackageCleaning(packageName, UserHandle.USER_ALL, true);
}
+
// writer
synchronized (mPackages) {
if (deletedPs != null) {
@@ -15268,7 +15289,7 @@
return false;
}
- prepareAppDataAfterInstall(newPkg);
+ prepareAppDataAfterInstallLIF(newPkg);
// writer
synchronized (mPackages) {
@@ -15594,7 +15615,7 @@
if (DEBUG_REMOVE) {
Slog.d(TAG, "Marking package:" + ps.name + " uninstalled for user:" + nextUserId);
}
- ps.setUserState(nextUserId, COMPONENT_ENABLED_STATE_DEFAULT,
+ ps.setUserState(nextUserId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
false /*installed*/, true /*stopped*/, true /*notLaunched*/,
false /*hidden*/, false /*suspended*/, null, null, null,
false /*blockUninstall*/,
@@ -15604,6 +15625,11 @@
private boolean clearPackageStateForUserLIF(PackageSetting ps, int userId,
PackageRemovedInfo outInfo) {
+ final PackageParser.Package pkg;
+ synchronized (mPackages) {
+ pkg = mPackages.get(ps.name);
+ }
+
final int[] userIds = (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds()
: new int[] {userId};
for (int nextUserId : userIds) {
@@ -15611,13 +15637,9 @@
Slog.d(TAG, "Updating package:" + ps.name + " install state for user:"
+ nextUserId);
}
- final int flags = StorageManager.FLAG_STORAGE_CE| StorageManager.FLAG_STORAGE_DE;
- try {
- mInstaller.destroyAppData(ps.volumeUuid, ps.name, nextUserId, flags);
- } catch (InstallerException e) {
- Slog.w(TAG, "Couldn't remove cache files for package " + ps.name, e);
- return false;
- }
+
+ destroyAppDataLIF(pkg, userId,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
removeKeystoreDataIfNeeded(nextUserId, ps.appId);
schedulePackageCleaning(ps.name, nextUserId, false);
synchronized (mPackages) {
@@ -15654,6 +15676,8 @@
}
private void clearExternalStorageDataSync(String packageName, int userId, boolean allData) {
+ if (DEFAULT_CONTAINER_PACKAGE.equals(packageName)) return;
+
final boolean mounted;
if (Environment.isExternalStorageEmulated()) {
mounted = true;
@@ -15713,14 +15737,15 @@
@Override
public void clearApplicationProfileData(String packageName) {
enforceSystemOrRoot("Only the system can clear all profile data");
- synchronized (mInstallLock) {
- try (PackageFreezer freezer = freezePackage(packageName,
- "clearApplicationProfileData")) {
- try {
- mInstaller.clearAppProfiles(packageName);
- } catch (InstallerException ex) {
- Log.e(TAG, "Could not clear profile data of package " + packageName);
- }
+
+ final PackageParser.Package pkg;
+ synchronized (mPackages) {
+ pkg = mPackages.get(packageName);
+ }
+
+ try (PackageFreezer freezer = freezePackage(packageName, "clearApplicationProfileData")) {
+ synchronized (mInstallLock) {
+ clearAppProfilesLIF(pkg);
}
}
}
@@ -15744,13 +15769,13 @@
public void run() {
mHandler.removeCallbacks(this);
final boolean succeeded;
- synchronized (mInstallLock) {
- try (PackageFreezer freezer = freezePackage(packageName,
- "clearApplicationUserData")) {
+ try (PackageFreezer freezer = freezePackage(packageName,
+ "clearApplicationUserData")) {
+ synchronized (mInstallLock) {
succeeded = clearApplicationUserDataLIF(packageName, userId);
}
+ clearExternalStorageDataSync(packageName, userId, true);
}
- clearExternalStorageDataSync(packageName, userId, true);
if (succeeded) {
// invoke DeviceStorageMonitor's update method to clear any notifications
DeviceStorageMonitorInternal dsm = LocalServices
@@ -15796,35 +15821,22 @@
resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, userId);
}
- // Always delete data directories for package, even if we found no other
- // record of app. This helps users recover from UID mismatches without
- // resorting to a full data wipe.
- // TODO: triage flags as part of 26466827
- final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
- try {
- mInstaller.clearAppData(pkg.volumeUuid, packageName, userId, flags);
- } catch (InstallerException e) {
- Slog.w(TAG, "Couldn't remove cache files for package " + packageName, e);
- return false;
- }
+ clearAppDataLIF(pkg, userId,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
removeKeystoreDataIfNeeded(userId, appId);
- // Create a native library symlink only if we have native libraries
- // and if the native libraries are 32 bit libraries. We do not provide
- // this symlink for 64 bit libraries.
- if (pkg.applicationInfo.primaryCpuAbi != null &&
- !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
- final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
- try {
- mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName,
- nativeLibPath, userId);
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed linking native library dir", e);
- return false;
- }
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ final int flags;
+ if (um.isUserUnlocked(userId)) {
+ flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
+ } else if (um.isUserRunning(userId)) {
+ flags = StorageManager.FLAG_STORAGE_DE;
+ } else {
+ flags = 0;
}
+ prepareAppDataContentsLIF(pkg, userId, flags);
return true;
}
@@ -15994,56 +16006,35 @@
android.Manifest.permission.DELETE_CACHE_FILES, null);
// Queue up an async operation since the package deletion may take a little while.
final int userId = UserHandle.getCallingUserId();
+
+ final PackageParser.Package pkg;
+ synchronized (mPackages) {
+ pkg = mPackages.get(packageName);
+ }
+
mHandler.post(new Runnable() {
public void run() {
- mHandler.removeCallbacks(this);
- final boolean succeded;
- synchronized (mInstallLock) {
- succeded = deleteApplicationCacheFilesLI(packageName, userId);
+ try (PackageFreezer freezer = freezePackage(packageName,
+ "deleteApplicationCacheFiles")) {
+ synchronized (mInstallLock) {
+ final int flags = StorageManager.FLAG_STORAGE_DE
+ | StorageManager.FLAG_STORAGE_CE;
+ clearAppDataLIF(pkg, userId, flags | Installer.FLAG_CLEAR_CACHE_ONLY);
+ clearAppDataLIF(pkg, userId, flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
+ clearExternalStorageDataSync(packageName, userId, false);
}
- clearExternalStorageDataSync(packageName, userId, false);
if (observer != null) {
try {
- observer.onRemoveCompleted(packageName, succeded);
+ observer.onRemoveCompleted(packageName, true);
} catch (RemoteException e) {
Log.i(TAG, "Observer no longer exists.");
}
- } //end if observer
- } //end run
+ }
+ }
});
}
- private boolean deleteApplicationCacheFilesLI(String packageName, int userId) {
- if (packageName == null) {
- Slog.w(TAG, "Attempt to delete null packageName.");
- return false;
- }
- PackageParser.Package p;
- synchronized (mPackages) {
- p = mPackages.get(packageName);
- }
- if (p == null) {
- Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
- return false;
- }
- final ApplicationInfo applicationInfo = p.applicationInfo;
- if (applicationInfo == null) {
- Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
- return false;
- }
- // TODO: triage flags as part of 26466827
- final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
- try {
- mInstaller.clearAppData(p.volumeUuid, packageName, userId,
- flags | Installer.FLAG_CLEAR_CACHE_ONLY);
- } catch (InstallerException e) {
- Slog.w(TAG, "Couldn't remove cache files for package "
- + packageName + " u" + userId, e);
- return false;
- }
- return true;
- }
-
@Override
public void getPackageSizeInfo(final String packageName, int userHandle,
final IPackageStatsObserver observer) {
@@ -16064,90 +16055,24 @@
mHandler.sendMessage(msg);
}
- private boolean getPackageSizeInfoLI(String packageName, int userHandle,
- PackageStats pStats) {
- if (packageName == null) {
- Slog.w(TAG, "Attempt to get size of null packageName.");
- return false;
- }
- PackageParser.Package p;
- boolean dataOnly = false;
- String libDirRoot = null;
- String asecPath = null;
- PackageSetting ps = null;
+ private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) {
+ final PackageSetting ps;
synchronized (mPackages) {
- p = mPackages.get(packageName);
ps = mSettings.mPackages.get(packageName);
- if(p == null) {
- dataOnly = true;
- if((ps == null) || (ps.pkg == null)) {
- Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
- return false;
- }
- p = ps.pkg;
- }
- if (ps != null) {
- libDirRoot = ps.legacyNativeLibraryPathString;
- }
- if (p != null && (p.isForwardLocked() || p.applicationInfo.isExternalAsec())) {
- final long token = Binder.clearCallingIdentity();
- try {
- String secureContainerId = cidFromCodePath(p.applicationInfo.getBaseCodePath());
- if (secureContainerId != null) {
- asecPath = PackageHelper.getSdFilesystem(secureContainerId);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
- String publicSrcDir = null;
- if(!dataOnly) {
- final ApplicationInfo applicationInfo = p.applicationInfo;
- if (applicationInfo == null) {
- Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
+ if (ps == null) {
+ Slog.w(TAG, "Failed to find settings for " + packageName);
return false;
}
- if (p.isForwardLocked()) {
- publicSrcDir = applicationInfo.getBaseResourcePath();
- }
}
- // TODO: extend to measure size of split APKs
- // TODO(multiArch): Extend getSizeInfo to look at the full subdirectory tree,
- // not just the first level.
- // TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not
- // just the primary.
- String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps));
-
- String apkPath;
- File packageDir = new File(p.codePath);
-
- if (packageDir.isDirectory() && p.canHaveOatDir()) {
- apkPath = packageDir.getAbsolutePath();
- // If libDirRoot is inside a package dir, set it to null to avoid it being counted twice
- if (libDirRoot != null && libDirRoot.startsWith(apkPath)) {
- libDirRoot = null;
- }
- } else {
- apkPath = p.baseCodePath;
- }
-
- // TODO: triage flags as part of 26466827
- final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
- mInstaller.getAppSize(p.volumeUuid, packageName, userHandle, flags, apkPath,
- libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
+ mInstaller.getAppSize(ps.volumeUuid, packageName, userId,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE,
+ ps.getCeDataInode(userId), ps.codePathString, stats);
+ return true;
} catch (InstallerException e) {
+ Slog.w(TAG, String.valueOf(e));
return false;
}
-
- // Fix-up for forward-locked applications in ASEC containers.
- if (!isExternal(p)) {
- pStats.codeSize += pStats.externalCodeSize;
- pStats.externalCodeSize = 0L;
- }
-
- return true;
}
private int getUidTargetSdkVersionLockedLPr(int uid) {
@@ -18553,7 +18478,9 @@
}
if (!Build.FINGERPRINT.equals(ver.fingerprint)) {
- deleteCodeCacheDirsLIF(ps.volumeUuid, ps.name);
+ clearAppDataLIF(ps.pkg, UserHandle.USER_ALL,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
}
}
@@ -18572,7 +18499,9 @@
}
sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, flags);
- reconcileAppsData(volumeUuid, user.id, flags);
+ synchronized (mInstallLock) {
+ reconcileAppsDataLI(volumeUuid, user.id, flags);
+ }
}
synchronized (mPackages) {
@@ -18764,7 +18693,9 @@
final StorageManager storage = mContext.getSystemService(StorageManager.class);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
- reconcileAppsData(volumeUuid, userId, flags);
+ synchronized (mInstallLock) {
+ reconcileAppsDataLI(volumeUuid, userId, flags);
+ }
}
}
@@ -18777,7 +18708,7 @@
* Verifies that directories exist and that ownership and labeling is
* correct for all installed apps.
*/
- private void reconcileAppsData(String volumeUuid, int userId, int flags) {
+ private void reconcileAppsDataLI(String volumeUuid, int userId, int flags) {
Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x"
+ Integer.toHexString(flags));
@@ -18804,9 +18735,11 @@
assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
} catch (PackageManagerException e) {
logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
- synchronized (mInstallLock) {
- destroyAppDataLI(volumeUuid, packageName, userId,
- StorageManager.FLAG_STORAGE_CE);
+ try {
+ mInstaller.destroyAppData(volumeUuid, packageName, userId,
+ StorageManager.FLAG_STORAGE_CE, 0);
+ } catch (InstallerException e2) {
+ logCriticalInfo(Log.WARN, "Failed to destroy: " + e2);
}
}
}
@@ -18821,9 +18754,11 @@
assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
} catch (PackageManagerException e) {
logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
- synchronized (mInstallLock) {
- destroyAppDataLI(volumeUuid, packageName, userId,
- StorageManager.FLAG_STORAGE_DE);
+ try {
+ mInstaller.destroyAppData(volumeUuid, packageName, userId,
+ StorageManager.FLAG_STORAGE_DE, 0);
+ } catch (InstallerException e2) {
+ logCriticalInfo(Log.WARN, "Failed to destroy: " + e2);
}
}
}
@@ -18846,12 +18781,12 @@
}
if (ps.getInstalled(userId)) {
- prepareAppData(volumeUuid, userId, flags, ps.pkg, restoreconNeeded);
+ prepareAppDataLIF(ps.pkg, userId, flags, restoreconNeeded);
- if (maybeMigrateAppData(volumeUuid, userId, ps.pkg)) {
+ if (maybeMigrateAppDataLIF(ps.pkg, userId)) {
// We may have just shuffled around app data directories, so
// prepare them one more time
- prepareAppData(volumeUuid, userId, flags, ps.pkg, restoreconNeeded);
+ prepareAppDataLIF(ps.pkg, userId, flags, restoreconNeeded);
}
preparedCount++;
@@ -18883,16 +18818,7 @@
* <p>
* <em>Note: To avoid a deadlock, do not call this method with {@code mPackages} lock held</em>
*/
- private void prepareAppDataAfterInstall(PackageParser.Package pkg) {
- prepareAppDataAfterInstallInternal(pkg);
- final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- PackageParser.Package childPackage = pkg.childPackages.get(i);
- prepareAppDataAfterInstallInternal(childPackage);
- }
- }
-
- private void prepareAppDataAfterInstallInternal(PackageParser.Package pkg) {
+ private void prepareAppDataAfterInstallLIF(PackageParser.Package pkg) {
final PackageSetting ps;
synchronized (mPackages) {
ps = mSettings.mPackages.get(pkg.packageName);
@@ -18913,7 +18839,7 @@
if (ps.getInstalled(user.id)) {
// Whenever an app changes, force a restorecon of its data
// TODO: when user data is locked, mark that we're still dirty
- prepareAppData(pkg.volumeUuid, user.id, flags, pkg, true);
+ prepareAppDataLIF(pkg, user.id, flags, true);
}
}
}
@@ -18926,57 +18852,112 @@
* will try recovering system apps by wiping data; third-party app data is
* left intact.
*/
- private void prepareAppData(String volumeUuid, int userId, int flags,
- PackageParser.Package pkg, boolean restoreconNeeded) {
+ private void prepareAppDataLIF(PackageParser.Package pkg, int userId, int flags,
+ boolean restoreconNeeded) {
+ if (pkg == null) {
+ Slog.wtf(TAG, "Package was null!", new Throwable());
+ return;
+ }
+ prepareAppDataLeafLIF(pkg, userId, flags, restoreconNeeded);
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ prepareAppDataLeafLIF(pkg.childPackages.get(i), userId, flags, restoreconNeeded);
+ }
+ }
+
+ private void prepareAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags,
+ boolean restoreconNeeded) {
if (DEBUG_APP_DATA) {
Slog.v(TAG, "prepareAppData for " + pkg.packageName + " u" + userId + " 0x"
+ Integer.toHexString(flags) + (restoreconNeeded ? " restoreconNeeded" : ""));
}
+ final String volumeUuid = pkg.volumeUuid;
final String packageName = pkg.packageName;
final ApplicationInfo app = pkg.applicationInfo;
final int appId = UserHandle.getAppId(app.uid);
Preconditions.checkNotNull(app.seinfo);
- synchronized (mInstallLock) {
- try {
- mInstaller.createAppData(volumeUuid, packageName, userId, flags,
- appId, app.seinfo, app.targetSdkVersion);
- } catch (InstallerException e) {
- if (app.isSystemApp()) {
- logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
- + ", but trying to recover: " + e);
- destroyAppDataLI(volumeUuid, packageName, userId, flags);
- try {
- mInstaller.createAppData(volumeUuid, packageName, userId, flags,
- appId, app.seinfo, app.targetSdkVersion);
- logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
- } catch (InstallerException e2) {
- logCriticalInfo(Log.DEBUG, "Recovery failed!");
- }
- } else {
- Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
+ try {
+ mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+ appId, app.seinfo, app.targetSdkVersion);
+ } catch (InstallerException e) {
+ if (app.isSystemApp()) {
+ logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+ + ", but trying to recover: " + e);
+ destroyAppDataLeafLIF(pkg, userId, flags);
+ try {
+ mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+ appId, app.seinfo, app.targetSdkVersion);
+ logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
+ } catch (InstallerException e2) {
+ logCriticalInfo(Log.DEBUG, "Recovery failed!");
}
+ } else {
+ Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
}
+ }
- if (restoreconNeeded) {
- restoreconAppDataLI(volumeUuid, packageName, userId, flags, appId, app.seinfo);
+ if (restoreconNeeded) {
+ try {
+ mInstaller.restoreconAppData(volumeUuid, packageName, userId, flags, appId,
+ app.seinfo);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Failed to restorecon for " + packageName + ": " + e);
}
+ }
- if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
- // Create a native library symlink only if we have native libraries
- // and if the native libraries are 32 bit libraries. We do not provide
- // this symlink for 64 bit libraries.
- if (app.primaryCpuAbi != null && !VMRuntime.is64BitAbi(app.primaryCpuAbi)) {
- final String nativeLibPath = app.nativeLibraryDir;
- try {
- mInstaller.linkNativeLibraryDirectory(volumeUuid, packageName,
- nativeLibPath, userId);
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to link native for " + packageName + ": " + e);
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
+ try {
+ // CE storage is unlocked right now, so read out the inode and
+ // remember for use later when it's locked
+ // TODO: mark this structure as dirty so we persist it!
+ final long ceDataInode = mInstaller.getAppDataInode(volumeUuid, packageName, userId,
+ StorageManager.FLAG_STORAGE_CE);
+ synchronized (mPackages) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ ps.setCeDataInode(ceDataInode, userId);
}
}
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Failed to find inode for " + packageName + ": " + e);
+ }
+ }
+
+ prepareAppDataContentsLeafLIF(pkg, userId, flags);
+ }
+
+ private void prepareAppDataContentsLIF(PackageParser.Package pkg, int userId, int flags) {
+ if (pkg == null) {
+ Slog.wtf(TAG, "Package was null!", new Throwable());
+ return;
+ }
+ prepareAppDataContentsLeafLIF(pkg, userId, flags);
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ prepareAppDataContentsLeafLIF(pkg.childPackages.get(i), userId, flags);
+ }
+ }
+
+ private void prepareAppDataContentsLeafLIF(PackageParser.Package pkg, int userId, int flags) {
+ final String volumeUuid = pkg.volumeUuid;
+ final String packageName = pkg.packageName;
+ final ApplicationInfo app = pkg.applicationInfo;
+
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
+ // Create a native library symlink only if we have native libraries
+ // and if the native libraries are 32 bit libraries. We do not provide
+ // this symlink for 64 bit libraries.
+ if (app.primaryCpuAbi != null && !VMRuntime.is64BitAbi(app.primaryCpuAbi)) {
+ final String nativeLibPath = app.nativeLibraryDir;
+ try {
+ mInstaller.linkNativeLibraryDirectory(volumeUuid, packageName,
+ nativeLibPath, userId);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Failed to link native for " + packageName + ": " + e);
+ }
}
}
}
@@ -18986,18 +18967,17 @@
* CE/DE data to match the {@code defaultToDeviceProtectedStorage} flag
* requested by the app.
*/
- private boolean maybeMigrateAppData(String volumeUuid, int userId, PackageParser.Package pkg) {
+ private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) {
if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage()
? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE;
- synchronized (mInstallLock) {
- try {
- mInstaller.migrateAppData(volumeUuid, pkg.packageName, userId, storageTarget);
- } catch (InstallerException e) {
- logCriticalInfo(Log.WARN,
- "Failed to migrate " + pkg.packageName + ": " + e.getMessage());
- }
+ try {
+ mInstaller.migrateAppData(pkg.volumeUuid, pkg.packageName, userId,
+ storageTarget);
+ } catch (InstallerException e) {
+ logCriticalInfo(Log.WARN,
+ "Failed to migrate " + pkg.packageName + ": " + e.getMessage());
}
return true;
} else {
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 2a96562..9d04472 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -323,6 +323,14 @@
return res;
}
+ long getCeDataInode(int userId) {
+ return readUserState(userId).ceDataInode;
+ }
+
+ void setCeDataInode(long ceDataInode, int userId) {
+ modifyUserState(userId).ceDataInode = ceDataInode;
+ }
+
boolean getStopped(int userId) {
return readUserState(userId).stopped;
}
@@ -363,12 +371,13 @@
modifyUserState(userId).blockUninstall = blockUninstall;
}
- void setUserState(int userId, int enabled, boolean installed, boolean stopped,
+ void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
boolean notLaunched, boolean hidden, boolean suspended,
String lastDisableAppCaller, ArraySet<String> enabledComponents,
ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState,
int linkGeneration) {
PackageUserState state = modifyUserState(userId);
+ state.ceDataInode = ceDataInode;
state.enabled = enabled;
state.installed = installed;
state.stopped = stopped;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 8f7cd5e..847f993 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -195,23 +195,26 @@
private static final String ATTR_NAME = "name";
private static final String ATTR_USER = "user";
private static final String ATTR_CODE = "code";
- private static final String ATTR_NOT_LAUNCHED = "nl";
- private static final String ATTR_ENABLED = "enabled";
private static final String ATTR_GRANTED = "granted";
private static final String ATTR_FLAGS = "flags";
- private static final String ATTR_ENABLED_CALLER = "enabledCaller";
+
+ private static final String ATTR_CE_DATA_INODE = "ceDataInode";
+ private static final String ATTR_INSTALLED = "inst";
private static final String ATTR_STOPPED = "stopped";
+ private static final String ATTR_NOT_LAUNCHED = "nl";
// Legacy, here for reading older versions of the package-restrictions.
private static final String ATTR_BLOCKED = "blocked";
// New name for the above attribute.
private static final String ATTR_HIDDEN = "hidden";
private static final String ATTR_SUSPENDED = "suspended";
- private static final String ATTR_INSTALLED = "inst";
private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
+ private static final String ATTR_ENABLED = "enabled";
+ private static final String ATTR_ENABLED_CALLER = "enabledCaller";
private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus";
+ private static final String ATTR_APP_LINK_GENERATION = "app-link-generation";
+
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_FINGERPRINT = "fingerprint";
- private static final String ATTR_APP_LINK_GENERATION = "app-link-generation";
private static final String ATTR_VOLUME_UUID = "volumeUuid";
private static final String ATTR_SDK_VERSION = "sdkVersion";
private static final String ATTR_DATABASE_VERSION = "databaseVersion";
@@ -790,7 +793,7 @@
|| (installUserId == UserHandle.USER_ALL
&& !isAdbInstallDisallowed(userManager, user.id))
|| installUserId == user.id;
- p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
+ p.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
installed,
true, // stopped,
true, // notLaunched
@@ -1574,7 +1577,7 @@
// in the stopped state, but not at first boot. Also
// consider all applications to be installed.
for (PackageSetting pkg : mPackages.values()) {
- pkg.setUserState(userId, COMPONENT_ENABLED_STATE_DEFAULT,
+ pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
true, // installed
false, // stopped
false, // notLaunched
@@ -1626,17 +1629,16 @@
XmlUtils.skipCurrentTag(parser);
continue;
}
- final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
- final int enabled = enabledStr == null
- ? COMPONENT_ENABLED_STATE_DEFAULT : Integer.parseInt(enabledStr);
- final String enabledCaller = parser.getAttributeValue(null,
- ATTR_ENABLED_CALLER);
- final String installedStr = parser.getAttributeValue(null, ATTR_INSTALLED);
- final boolean installed = installedStr == null
- ? true : Boolean.parseBoolean(installedStr);
- final String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED);
- final boolean stopped = stoppedStr == null
- ? false : Boolean.parseBoolean(stoppedStr);
+
+ final long ceDataInode = XmlUtils.readLongAttribute(parser, ATTR_CE_DATA_INODE,
+ 0);
+ final boolean installed = XmlUtils.readBooleanAttribute(parser, ATTR_INSTALLED,
+ true);
+ final boolean stopped = XmlUtils.readBooleanAttribute(parser, ATTR_STOPPED,
+ false);
+ final boolean notLaunched = XmlUtils.readBooleanAttribute(parser,
+ ATTR_NOT_LAUNCHED, false);
+
// For backwards compatibility with the previous name of "blocked", which
// now means hidden, read the old attribute as well.
final String blockedStr = parser.getAttributeValue(null, ATTR_BLOCKED);
@@ -1645,25 +1647,21 @@
final String hiddenStr = parser.getAttributeValue(null, ATTR_HIDDEN);
hidden = hiddenStr == null
? hidden : Boolean.parseBoolean(hiddenStr);
- final String suspendedStr = parser.getAttributeValue(null, ATTR_SUSPENDED);
- final boolean suspended = suspendedStr == null
- ? false : Boolean.parseBoolean(suspendedStr);
- final String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED);
- final boolean notLaunched = stoppedStr == null
- ? false : Boolean.parseBoolean(notLaunchedStr);
- final String blockUninstallStr = parser.getAttributeValue(null,
- ATTR_BLOCK_UNINSTALL);
- final boolean blockUninstall = blockUninstallStr == null
- ? false : Boolean.parseBoolean(blockUninstallStr);
- final String verifStateStr =
- parser.getAttributeValue(null, ATTR_DOMAIN_VERIFICATON_STATE);
- final int verifState = (verifStateStr == null) ?
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED :
- Integer.parseInt(verifStateStr);
+ final boolean suspended = XmlUtils.readBooleanAttribute(parser, ATTR_SUSPENDED,
+ false);
+ final boolean blockUninstall = XmlUtils.readBooleanAttribute(parser,
+ ATTR_BLOCK_UNINSTALL, false);
+ final int enabled = XmlUtils.readIntAttribute(parser, ATTR_ENABLED,
+ COMPONENT_ENABLED_STATE_DEFAULT);
+ final String enabledCaller = parser.getAttributeValue(null,
+ ATTR_ENABLED_CALLER);
- final String linkGenStr = parser.getAttributeValue(null, ATTR_APP_LINK_GENERATION);
- final int linkGeneration = linkGenStr == null ? 0 : Integer.parseInt(linkGenStr);
+ final int verifState = XmlUtils.readIntAttribute(parser,
+ ATTR_DOMAIN_VERIFICATON_STATE,
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
+ final int linkGeneration = XmlUtils.readIntAttribute(parser,
+ ATTR_APP_LINK_GENERATION, 0);
if (linkGeneration > maxAppLinkGeneration) {
maxAppLinkGeneration = linkGeneration;
}
@@ -1687,8 +1685,8 @@
}
}
- ps.setUserState(userId, enabled, installed, stopped, notLaunched, hidden,
- suspended, enabledCaller, enabledComponents, disabledComponents,
+ ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
+ hidden, suspended, enabledCaller, enabledComponents, disabledComponents,
blockUninstall, verifState, linkGeneration);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
@@ -1927,80 +1925,69 @@
serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
for (final PackageSetting pkg : mPackages.values()) {
- PackageUserState ustate = pkg.readUserState(userId);
- if (ustate.stopped || ustate.notLaunched || !ustate.installed
- || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT
- || ustate.hidden
- || ustate.suspended
- || (ustate.enabledComponents != null
- && ustate.enabledComponents.size() > 0)
- || (ustate.disabledComponents != null
- && ustate.disabledComponents.size() > 0)
- || ustate.blockUninstall
- || (ustate.domainVerificationStatus !=
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED)) {
- serializer.startTag(null, TAG_PACKAGE);
- serializer.attribute(null, ATTR_NAME, pkg.name);
- if (DEBUG_MU) Log.i(TAG, " pkg=" + pkg.name + ", state=" + ustate.enabled);
+ final PackageUserState ustate = pkg.readUserState(userId);
+ if (DEBUG_MU) Log.i(TAG, " pkg=" + pkg.name + ", state=" + ustate.enabled);
- if (!ustate.installed) {
- serializer.attribute(null, ATTR_INSTALLED, "false");
- }
- if (ustate.stopped) {
- serializer.attribute(null, ATTR_STOPPED, "true");
- }
- if (ustate.notLaunched) {
- serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
- }
- if (ustate.hidden) {
- serializer.attribute(null, ATTR_HIDDEN, "true");
- }
- if (ustate.suspended) {
- serializer.attribute(null, ATTR_SUSPENDED, "true");
- }
- if (ustate.blockUninstall) {
- serializer.attribute(null, ATTR_BLOCK_UNINSTALL, "true");
- }
- if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
- serializer.attribute(null, ATTR_ENABLED,
- Integer.toString(ustate.enabled));
- if (ustate.lastDisableAppCaller != null) {
- serializer.attribute(null, ATTR_ENABLED_CALLER,
- ustate.lastDisableAppCaller);
- }
- }
- if (ustate.domainVerificationStatus !=
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
- serializer.attribute(null, ATTR_DOMAIN_VERIFICATON_STATE,
- Integer.toString(ustate.domainVerificationStatus));
- }
- if (ustate.appLinkGeneration != 0) {
- serializer.attribute(null, ATTR_APP_LINK_GENERATION,
- Integer.toString(ustate.appLinkGeneration));
- }
- if (ustate.enabledComponents != null
- && ustate.enabledComponents.size() > 0) {
- serializer.startTag(null, TAG_ENABLED_COMPONENTS);
- for (final String name : ustate.enabledComponents) {
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- serializer.endTag(null, TAG_ENABLED_COMPONENTS);
- }
- if (ustate.disabledComponents != null
- && ustate.disabledComponents.size() > 0) {
- serializer.startTag(null, TAG_DISABLED_COMPONENTS);
- for (final String name : ustate.disabledComponents) {
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- serializer.endTag(null, TAG_DISABLED_COMPONENTS);
- }
-
- serializer.endTag(null, TAG_PACKAGE);
+ serializer.startTag(null, TAG_PACKAGE);
+ serializer.attribute(null, ATTR_NAME, pkg.name);
+ if (ustate.ceDataInode != 0) {
+ XmlUtils.writeLongAttribute(serializer, ATTR_CE_DATA_INODE, ustate.ceDataInode);
}
+ if (!ustate.installed) {
+ serializer.attribute(null, ATTR_INSTALLED, "false");
+ }
+ if (ustate.stopped) {
+ serializer.attribute(null, ATTR_STOPPED, "true");
+ }
+ if (ustate.notLaunched) {
+ serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
+ }
+ if (ustate.hidden) {
+ serializer.attribute(null, ATTR_HIDDEN, "true");
+ }
+ if (ustate.suspended) {
+ serializer.attribute(null, ATTR_SUSPENDED, "true");
+ }
+ if (ustate.blockUninstall) {
+ serializer.attribute(null, ATTR_BLOCK_UNINSTALL, "true");
+ }
+ if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
+ serializer.attribute(null, ATTR_ENABLED,
+ Integer.toString(ustate.enabled));
+ if (ustate.lastDisableAppCaller != null) {
+ serializer.attribute(null, ATTR_ENABLED_CALLER,
+ ustate.lastDisableAppCaller);
+ }
+ }
+ if (ustate.domainVerificationStatus !=
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ XmlUtils.writeIntAttribute(serializer, ATTR_DOMAIN_VERIFICATON_STATE,
+ ustate.domainVerificationStatus);
+ }
+ if (ustate.appLinkGeneration != 0) {
+ XmlUtils.writeIntAttribute(serializer, ATTR_APP_LINK_GENERATION,
+ ustate.appLinkGeneration);
+ }
+ if (!ArrayUtils.isEmpty(ustate.enabledComponents)) {
+ serializer.startTag(null, TAG_ENABLED_COMPONENTS);
+ for (final String name : ustate.enabledComponents) {
+ serializer.startTag(null, TAG_ITEM);
+ serializer.attribute(null, ATTR_NAME, name);
+ serializer.endTag(null, TAG_ITEM);
+ }
+ serializer.endTag(null, TAG_ENABLED_COMPONENTS);
+ }
+ if (!ArrayUtils.isEmpty(ustate.disabledComponents)) {
+ serializer.startTag(null, TAG_DISABLED_COMPONENTS);
+ for (final String name : ustate.disabledComponents) {
+ serializer.startTag(null, TAG_ITEM);
+ serializer.attribute(null, ATTR_NAME, name);
+ serializer.endTag(null, TAG_ITEM);
+ }
+ serializer.endTag(null, TAG_DISABLED_COMPONENTS);
+ }
+
+ serializer.endTag(null, TAG_PACKAGE);
}
writePreferredActivitiesLPr(serializer, userId, true);
@@ -4532,6 +4519,8 @@
for (UserInfo user : users) {
pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": ");
+ pw.print("ceDataInode=");
+ pw.print(ps.getCeDataInode(user.id));
pw.print(" installed=");
pw.print(ps.getInstalled(user.id));
pw.print(" hidden=");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7f40079..c991130 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -642,13 +642,14 @@
* Subtracts the insets calculated by intersecting {@param layoutFrame} with {@param insetFrame}
* from {@param frame}. In other words, it applies the insets that would result if
* {@param frame} would be shifted to {@param layoutFrame} and then applying the insets from
- * {@param insetFrame}.
+ * {@param insetFrame}. Also it respects {@param displayFrame} in case window has minimum
+ * width/height applied and insets should be overridden.
*/
- private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame) {
- final int left = Math.max(0, insetFrame.left - layoutFrame.left);
- final int top = Math.max(0, insetFrame.top - layoutFrame.top);
- final int right = Math.max(0, layoutFrame.right - insetFrame.right);
- final int bottom = Math.max(0, layoutFrame.bottom - insetFrame.bottom);
+ private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame, Rect displayFrame) {
+ final int left = Math.max(0, insetFrame.left - Math.max(layoutFrame.left, displayFrame.left));
+ final int top = Math.max(0, insetFrame.top - Math.max(layoutFrame.top, displayFrame.top));
+ final int right = Math.max(0, Math.min(layoutFrame.right, displayFrame.right) - insetFrame.right);
+ final int bottom = Math.max(0, Math.min(layoutFrame.bottom, displayFrame.bottom) - insetFrame.bottom);
frame.inset(left, top, right, bottom);
}
@@ -687,8 +688,7 @@
// The offset from the layout containing frame to the actual containing frame.
final int layoutXDiff;
final int layoutYDiff;
- if (mInsetFrame.isEmpty() && (fullscreenTask
- || layoutInParentFrame())) {
+ if (mInsetFrame.isEmpty() && (fullscreenTask || layoutInParentFrame())) {
// We use the parent frame as the containing frame for fullscreen and child windows
mContainingFrame.set(pf);
mDisplayFrame.set(df);
@@ -733,10 +733,12 @@
layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
- subtractInsets(mDisplayFrame, layoutContainingFrame, df);
+ mTmpRect.set(0, 0, mDisplayContent.getDisplayInfo().logicalWidth,
+ mDisplayContent.getDisplayInfo().logicalHeight);
+ subtractInsets(mDisplayFrame, layoutContainingFrame, df, mTmpRect);
if (!layoutInParentFrame()) {
- subtractInsets(mContainingFrame, layoutContainingFrame, pf);
- subtractInsets(mInsetFrame, layoutContainingFrame, pf);
+ subtractInsets(mContainingFrame, layoutContainingFrame, pf, mTmpRect);
+ subtractInsets(mInsetFrame, layoutContainingFrame, pf, mTmpRect);
}
layoutDisplayFrame = df;
layoutDisplayFrame.intersect(layoutContainingFrame);
@@ -855,8 +857,8 @@
getDisplayContent().getLogicalDisplayRect(mTmpRect);
// Override right and/or bottom insets in case if the frame doesn't fit the screen in
// non-fullscreen mode.
- boolean overrideRightInset = !fullscreenTask && mFrame.right > mTmpRect.right;
- boolean overrideBottomInset = !fullscreenTask && mFrame.bottom > mTmpRect.bottom;
+ boolean overrideRightInset = !fullscreenTask && layoutContainingFrame.right > mTmpRect.right;
+ boolean overrideBottomInset = !fullscreenTask && layoutContainingFrame.bottom > mTmpRect.bottom;
mContentInsets.set(mContentFrame.left - layoutContainingFrame.left,
mContentFrame.top - layoutContainingFrame.top,
overrideRightInset ? mTmpRect.right - mContentFrame.right
@@ -2590,7 +2592,7 @@
final int ph = containingFrame.height();
final Task task = getTask();
final boolean nonFullscreenTask = isInMultiWindowMode();
- final boolean fitToDisplay = task != null && !task.isFloating() && !layoutInParentFrame();
+ final boolean fitToDisplay = task != null && !nonFullscreenTask && !layoutInParentFrame();
float x, y;
int w,h;