Merge "Register functor draw correctly"
diff --git a/Android.mk b/Android.mk
index e324a75..749242d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -326,6 +326,7 @@
core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
core/java/android/view/autofill/IAutoFillManager.aidl \
core/java/android/view/autofill/IAutoFillManagerClient.aidl \
+ core/java/android/view/autofill/IAutofillWindowPresenter.aidl \
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IDockedStackListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index f8c1c6a..8f969c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -879,6 +879,7 @@
field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
field public static final int matchOrder = 16843855; // 0x101044f
field public static final int max = 16843062; // 0x1010136
+ field public static final int maxAspectRatio = 16844131; // 0x1010563
field public static final int maxButtonHeight = 16844029; // 0x10104fd
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/removed.txt b/api/removed.txt
index 8acf4ad..75da976 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -380,16 +380,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 65588fb..48b878e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -992,6 +992,7 @@
field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
field public static final int matchOrder = 16843855; // 0x101044f
field public static final int max = 16843062; // 0x1010136
+ field public static final int maxAspectRatio = 16844131; // 0x1010563
field public static final int maxButtonHeight = 16844029; // 0x10104fd
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/system-removed.txt b/api/system-removed.txt
index a2fcbcd..3aa9398 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -374,16 +374,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/test-current.txt b/api/test-current.txt
index 8262223..4d8d7f2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -879,6 +879,7 @@
field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
field public static final int matchOrder = 16843855; // 0x101044f
field public static final int max = 16843062; // 0x1010136
+ field public static final int maxAspectRatio = 16844131; // 0x1010563
field public static final int maxButtonHeight = 16844029; // 0x10104fd
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 8acf4ad..75da976 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -380,16 +380,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index ffc0f87..db17b28 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -53,15 +53,15 @@
String arg = nextArg();
if (arg.equals("backup")) {
- doFullBackup(OsConstants.STDOUT_FILENO);
+ doBackup(OsConstants.STDOUT_FILENO);
} else if (arg.equals("restore")) {
- doFullRestore(OsConstants.STDIN_FILENO);
+ doRestore(OsConstants.STDIN_FILENO);
} else {
Log.e(TAG, "Invalid operation '" + arg + "'");
}
}
- private void doFullBackup(int socketFd) {
+ private void doBackup(int socketFd) {
ArrayList<String> packages = new ArrayList<String>();
boolean saveApks = false;
boolean saveObbs = false;
@@ -70,6 +70,7 @@
boolean doWidgets = false;
boolean allIncludesSystem = true;
boolean doCompress = true;
+ boolean doKeyValue = false;
String arg;
while ((arg = nextArg()) != null) {
@@ -100,6 +101,8 @@
doCompress = true;
} else if ("-nocompress".equals(arg)) {
doCompress = false;
+ } else if ("-includekeyvalue".equals(arg)) {
+ doKeyValue = true;
} else {
Log.w(TAG, "Unknown backup flag " + arg);
continue;
@@ -123,8 +126,8 @@
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
String[] packArray = new String[packages.size()];
- mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
- doEverything, allIncludesSystem, doCompress, packages.toArray(packArray));
+ mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
+ allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for backup");
} finally {
@@ -136,12 +139,12 @@
}
}
- private void doFullRestore(int socketFd) {
+ private void doRestore(int socketFd) {
// No arguments to restore
ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
- mBackupManager.fullRestore(fd);
+ mBackupManager.adbRestore(fd);
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for restore");
} finally {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index db3772d..658d662 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -94,6 +94,8 @@
runGetFbeMode();
} else if ("fstrim".equals(op)) {
runFstrim();
+ } else if ("set-virtual-disk".equals(op)) {
+ runSetVirtualDisk();
} else {
throw new IllegalArgumentException();
}
@@ -225,6 +227,12 @@
mSm.fstrim(0);
}
+ public void runSetVirtualDisk() throws RemoteException {
+ final boolean virtualDisk = Boolean.parseBoolean(nextArg());
+ mSm.setDebugFlags(virtualDisk ? StorageManager.DEBUG_VIRTUAL_DISK : 0,
+ StorageManager.DEBUG_VIRTUAL_DISK);
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -240,6 +248,7 @@
System.err.println(" sm has-adoptable");
System.err.println(" sm get-primary-storage-uuid");
System.err.println(" sm set-force-adoptable [true|false]");
+ System.err.println(" sm set-virtual-disk [true|false]");
System.err.println("");
System.err.println(" sm partition DISK [public|private|mixed] [ratio]");
System.err.println(" sm mount VOLUME");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4fd3f39..fa1de03 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,9 +17,12 @@
package android.app;
import android.metrics.LogMaker;
+import android.graphics.Rect;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
@@ -768,7 +771,6 @@
/*package*/ Configuration mCurrentConfig;
private SearchManager mSearchManager;
private MenuInflater mMenuInflater;
- private final MetricsLogger mMetricsLogger = new MetricsLogger();
static final class NonConfigurationInstances {
Object activity;
@@ -850,6 +852,8 @@
private boolean mAutoFillResetNeeded;
+ private AutofillPopupWindow mAutofillPopupWindow;
+
private static native String getDlWarning();
/** Return the intent that started this activity. */
@@ -7190,60 +7194,7 @@
/** @hide */
@Override
- public void autofill(List<AutofillId> ids, List<AutofillValue> values) {
- final View root = getWindow().getDecorView();
- final int itemCount = ids.size();
- int numApplied = 0;
- ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
-
- for (int i = 0; i < itemCount; i++) {
- final AutofillId id = ids.get(i);
- final AutofillValue value = values.get(i);
- final int viewId = id.getViewId();
- final View view = root.findViewByAccessibilityIdTraversal(viewId);
- if (view == null) {
- Log.w(TAG, "autofill(): no View with id " + viewId);
- continue;
- }
- if (id.isVirtual()) {
- final int parentId = id.getViewId();
- if (virtualValues == null) {
- // Most likely there will be just one view with virtual children.
- virtualValues = new ArrayMap<>(1);
- }
- SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
- if (valuesByParent == null) {
- // We don't know the size yet, but usually it will be just a few fields...
- valuesByParent = new SparseArray<>(5);
- virtualValues.put(view, valuesByParent);
- }
- valuesByParent.put(id.getVirtualChildId(), value);
- } else {
- if (view.autofill(value)) {
- numApplied++;
- }
- }
- }
-
- if (virtualValues != null) {
- for (int i = 0; i < virtualValues.size(); i++) {
- final View parent = virtualValues.keyAt(i);
- final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
- if (parent.autofill(childrenValues)) {
- numApplied += childrenValues.size();
- }
- }
- }
-
- final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
- mMetricsLogger.write(log);
- }
-
- /** @hide */
- @Override
- public void authenticate(IntentSender intent, Intent fillInIntent) {
+ final public void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent) {
try {
startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
0, fillInIntent, 0, 0, null);
@@ -7254,10 +7205,47 @@
/** @hide */
@Override
- public void resetableStateAvailable() {
+ final public void autofillCallbackResetableStateAvailable() {
mAutoFillResetNeeded = true;
}
+ /** @hide */
+ @Override
+ final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width,
+ int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final Rect actualAnchorBounds = new Rect();
+ anchor.getBoundsOnScreen(actualAnchorBounds);
+
+ final int offsetX = (anchorBounds != null)
+ ? anchorBounds.left - actualAnchorBounds.left : 0;
+ int offsetY = (anchorBounds != null)
+ ? anchorBounds.top - actualAnchorBounds.top : 0;
+
+ final boolean wasShowing;
+
+ if (mAutofillPopupWindow == null) {
+ wasShowing = false;
+ mAutofillPopupWindow = new AutofillPopupWindow(presenter);
+ } else {
+ wasShowing = mAutofillPopupWindow.isShowing();
+ }
+ mAutofillPopupWindow.update(anchor, offsetX, offsetY, width, height, anchorBounds,
+ actualAnchorBounds);
+
+ return !wasShowing && mAutofillPopupWindow.isShowing();
+ }
+
+ /** @hide */
+ @Override
+ final public boolean autofillCallbackRequestHideFillUi() {
+ if (mAutofillPopupWindow == null) {
+ return false;
+ }
+ mAutofillPopupWindow.dismiss();
+ mAutofillPopupWindow = null;
+ return true;
+ }
+
/**
* If set to true, this indicates to the system that it should never take a
* screenshot of the activity to be used as a representation while it is not in a started state.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b9c888c..4c080c9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -367,6 +367,7 @@
// STOPSHIP: fix buggy apps
if (SystemProperties.getBoolean("fw.ignore_buggy", false)) return false;
if ("com.google.android.tts".equals(getApplicationInfo().packageName)) return true;
+ if ("com.breel.geswallpapers".equals(getApplicationInfo().packageName)) return true;
return false;
}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 7f0b6fb..cae54b6 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -53,21 +53,6 @@
int getNightMode();
/**
- * Sets whith theme overlays to use within /vendor/overlay.
- */
- void setTheme(String theme);
-
- /**
- * Gets which theme overlays to use within /vendor/overlay.
- */
- String getTheme();
-
- /**
- * Gets the themes available in /vendor/overlay.
- */
- String[] getAvailableThemes();
-
- /**
* Tells if UI mode is locked or not.
*/
boolean isUiModeLocked();
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index af41a7d..07e2570 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -242,50 +242,6 @@
}
/**
- * Sets the vendor theme overlay property, then triggers a reboot.
- * @hide
- */
- public void setTheme(String theme) {
- if (mService != null) {
- try {
- mService.setTheme(theme);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
- /**
- * Gets the vendor theme overlay property.
- * @hide
- */
- public String getTheme() {
- if (mService != null) {
- try {
- return mService.getTheme();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return null;
- }
-
- /**
- * Gets the valid inputs to {@link #setTheme(String)}.
- * @hide
- */
- public String[] getAvailableThemes() {
- if (mService != null) {
- try {
- return mService.getAvailableThemes();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return null;
- }
-
- /**
* Returns the currently configured night mode.
* <p>
* May be one of:
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 391885d..6d8d5e9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7207,7 +7207,7 @@
* @return {@code true} if security logging is enabled by device owner, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner.
*/
- public boolean isSecurityLoggingEnabled(@NonNull ComponentName admin) {
+ public boolean isSecurityLoggingEnabled(@Nullable ComponentName admin) {
throwIfParentInstance("isSecurityLoggingEnabled");
try {
return mService.isSecurityLoggingEnabled(admin);
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 76828ee..a5dd5bd 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -56,6 +56,7 @@
public static final String APK_TREE_TOKEN = "a";
public static final String OBB_TREE_TOKEN = "obb";
+ public static final String KEY_VALUE_DATA_TOKEN = "k";
public static final String ROOT_TREE_TOKEN = "r";
public static final String FILES_TREE_TOKEN = "f";
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 59a941a..9c3b110 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -144,9 +144,10 @@
void backupNow();
/**
- * Write a full backup of the given package to the supplied file descriptor.
+ * Write a backup of the given package to the supplied file descriptor.
* The fd may be a socket or other non-seekable destination. If no package names
* are supplied, then every application on the device will be backed up to the output.
+ * Currently only used by the 'adb backup' command.
*
* <p>This method is <i>synchronous</i> -- it does not return until the backup has
* completed.
@@ -167,12 +168,14 @@
* as including packages pre-installed as part of the system. If {@code false},
* then setting {@code allApps} to {@code true} will mean only that all 3rd-party
* applications will be included in the dataset.
+ * @param doKeyValue If {@code true}, also packages supporting key-value backup will be backed
+ * up. If {@code false}, key-value packages will be skipped.
* @param packageNames The package names of the apps whose data (and optionally .apk files)
* are to be backed up. The <code>allApps</code> parameter supersedes this.
*/
- void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
- boolean doCompress, in String[] packageNames);
+ boolean doCompress, boolean doKeyValue, in String[] packageNames);
/**
* Perform a full-dataset backup of the given applications via the currently active
@@ -184,11 +187,12 @@
/**
* Restore device content from the data stream passed through the given socket. The
- * data stream must be in the format emitted by fullBackup().
+ * data stream must be in the format emitted by adbBackup().
+ * Currently only used by the 'adb restore' command.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
*/
- void fullRestore(in ParcelFileDescriptor fd);
+ void adbRestore(in ParcelFileDescriptor fd);
/**
* Confirm that the requested full backup/restore operation can proceed. The system will
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 6b3ef4a..9e2eb84 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -141,6 +141,7 @@
* Application interface registered - app is ready to go
* @hide
*/
+ @Override
public void onClientRegistered(int status, int clientIf) {
if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
+ " clientIf=" + clientIf);
@@ -210,6 +211,7 @@
* Client connection state changed
* @hide
*/
+ @Override
public void onClientConnectionState(int status, int clientIf,
boolean connected, String address) {
if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
@@ -245,6 +247,7 @@
* we are done at this point.
* @hide
*/
+ @Override
public void onSearchComplete(String address, List<BluetoothGattService> services,
int status) {
if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
@@ -288,6 +291,7 @@
* Updates the internal value.
* @hide
*/
+ @Override
public void onCharacteristicRead(String address, int status, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
+ " handle=" + handle + " Status=" + status);
@@ -336,6 +340,7 @@
* Let the app know how we did...
* @hide
*/
+ @Override
public void onCharacteristicWrite(String address, int status, int handle) {
if (VDBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
+ " handle=" + handle + " Status=" + status);
@@ -380,6 +385,7 @@
* Updates the internal value.
* @hide
*/
+ @Override
public void onNotify(String address, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
@@ -403,6 +409,7 @@
* Descriptor has been read.
* @hide
*/
+ @Override
public void onDescriptorRead(String address, int status, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " handle=" + handle);
@@ -446,6 +453,7 @@
* Descriptor write operation complete.
* @hide
*/
+ @Override
public void onDescriptorWrite(String address, int status, int handle) {
if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " handle=" + handle);
@@ -488,6 +496,7 @@
* Prepared write transaction completed (or aborted)
* @hide
*/
+ @Override
public void onExecuteWrite(String address, int status) {
if (VDBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
+ " status=" + status);
@@ -510,6 +519,7 @@
* Remote device RSSI has been read
* @hide
*/
+ @Override
public void onReadRemoteRssi(String address, int rssi, int status) {
if (VDBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
" rssi=" + rssi + " status=" + status);
@@ -527,6 +537,7 @@
* Callback invoked when the MTU for a given connection changes
* @hide
*/
+ @Override
public void onConfigureMTU(String address, int mtu, int status) {
if (DBG) Log.d(TAG, "onConfigureMTU() - Device=" + address +
" mtu=" + mtu + " status=" + status);
@@ -539,6 +550,27 @@
Log.w(TAG, "Unhandled exception in callback", ex);
}
}
+
+ /**
+ * Callback invoked when the given connection is updated
+ * @hide
+ */
+ @Override
+ public void onConnectionUpdated(String address, int interval, int latency,
+ int timeout, int status) {
+ if (DBG) Log.d(TAG, "onConnectionUpdated() - Device=" + address +
+ " interval=" + interval + " latency=" + latency +
+ " timeout=" + timeout + " status=" + status);
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ try {
+ mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+ timeout, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception in callback", ex);
+ }
+ }
};
/*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index be69df9..11a15c6 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -179,4 +179,22 @@
*/
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
}
+
+ /**
+ * Callback indicating the connection parameters were updated.
+ *
+ * @param gatt GATT client involved
+ * @param interval Connection interval used on this connection, 1.25ms unit. Valid
+ * range is from 6 (7.5ms) to 3200 (4000ms).
+ * @param latency Slave latency for the connection in number of connection events. Valid
+ * range is from 0 to 499
+ * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is
+ * from 10 (0.1s) to 3200 (32s)
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+ * successfully
+ * @hide
+ */
+ public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
+ int status) {
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 1bd7bd4..c991e2f 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -65,6 +65,7 @@
* Application interface registered - app is ready to go
* @hide
*/
+ @Override
public void onServerRegistered(int status, int serverIf) {
if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
+ " serverIf=" + serverIf);
@@ -80,18 +81,10 @@
}
/**
- * Callback reporting an LE scan result.
- * @hide
- */
- public void onScanResult(String address, int rssi, byte[] advData) {
- if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
- // no op
- }
-
- /**
* Server connection state changed
* @hide
*/
+ @Override
public void onServerConnectionState(int status, int serverIf,
boolean connected, String address) {
if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
@@ -109,6 +102,7 @@
* Service has been added
* @hide
*/
+ @Override
public void onServiceAdded(int status, BluetoothGattService service) {
if (DBG) Log.d(TAG, "onServiceAdded() - handle=" + service.getInstanceId()
+ " uuid=" + service.getUuid() + " status=" + status);
@@ -149,6 +143,7 @@
* Remote client characteristic read request.
* @hide
*/
+ @Override
public void onCharacteristicReadRequest(String address, int transId,
int offset, boolean isLong, int handle) {
if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
@@ -171,6 +166,7 @@
* Remote client descriptor read request.
* @hide
*/
+ @Override
public void onDescriptorReadRequest(String address, int transId,
int offset, boolean isLong, int handle) {
if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
@@ -193,6 +189,7 @@
* Remote client characteristic write request.
* @hide
*/
+ @Override
public void onCharacteristicWriteRequest(String address, int transId,
int offset, int length, boolean isPrep, boolean needRsp,
int handle, byte[] value) {
@@ -218,6 +215,7 @@
* Remote client descriptor write request.
* @hide
*/
+ @Override
public void onDescriptorWriteRequest(String address, int transId, int offset,
int length, boolean isPrep, boolean needRsp, int handle, byte[] value) {
if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle);
@@ -241,6 +239,7 @@
* Execute pending writes.
* @hide
*/
+ @Override
public void onExecuteWrite(String address, int transId,
boolean execWrite) {
if (DBG) Log.d(TAG, "onExecuteWrite() - "
@@ -261,6 +260,7 @@
* A notification/indication has been sent.
* @hide
*/
+ @Override
public void onNotificationSent(String address, int status) {
if (VDBG) Log.d(TAG, "onNotificationSent() - "
+ "device=" + address + ", status=" + status);
@@ -279,6 +279,7 @@
* The MTU for a connection has changed
* @hide
*/
+ @Override
public void onMtuChanged(String address, int mtu) {
if (DBG) Log.d(TAG, "onMtuChanged() - "
+ "device=" + address + ", mtu=" + mtu);
@@ -297,6 +298,7 @@
* The PHY for a connection was updated
* @hide
*/
+ @Override
public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+ ", rxPHy=" + rxPhy);
@@ -315,6 +317,7 @@
* The PHY for a connection was read
* @hide
*/
+ @Override
public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+ ", rxPHy=" + rxPhy);
@@ -328,6 +331,28 @@
Log.w(TAG, "Unhandled exception: " + ex);
}
}
+
+ /**
+ * Callback invoked when the given connection is updated
+ * @hide
+ */
+ @Override
+ public void onConnectionUpdated(String address, int interval, int latency,
+ int timeout, int status) {
+ if (DBG) Log.d(TAG, "onConnectionUpdated() - Device=" + address +
+ " interval=" + interval + " latency=" + latency +
+ " timeout=" + timeout + " status=" + status);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ if (device == null) return;
+
+ try {
+ mCallback.onConnectionUpdated(device, interval, latency,
+ timeout, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
};
/**
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 0a89072..3b8f962 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -184,4 +184,23 @@
*/
public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
}
+
+ /**
+ * Callback indicating the connection parameters were updated.
+ *
+ * @param device The remote device involved
+ * @param interval Connection interval used on this connection, 1.25ms unit. Valid
+ * range is from 6 (7.5ms) to 3200 (4000ms).
+ * @param latency Slave latency for the connection in number of connection events. Valid
+ * range is from 0 to 499
+ * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is
+ * from 10 (0.1s) to 3200 (32s)
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+ * successfully
+ * @hide
+ */
+ public void onConnectionUpdated(BluetoothDevice gatt, int interval, int latency, int timeout,
+ int status) {
+ }
+
}
diff --git a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
index 736f4b2..ed69e54 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
@@ -37,4 +37,6 @@
void onNotify(in String address, in int handle, in byte[] value);
void onReadRemoteRssi(in String address, in int rssi, in int status);
void onConfigureMTU(in String address, in int mtu, in int status);
+ void onConnectionUpdated(in String address, in int interval, in int latency,
+ in int timeout, in int status);
}
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
index 091ffb3..267e882 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
@@ -42,4 +42,6 @@
void onMtuChanged(in String address, in int mtu);
void onPhyUpdate(in String address, in int txPhy, in int rxPhy, in int status);
void onPhyRead(in String address, in int txPhy, in int rxPhy, in int status);
+ void onConnectionUpdated(in String address, in int interval, in int latency,
+ in int timeout, in int status);
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index ecdc0ce..78e3de4 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -214,7 +214,7 @@
}
private boolean checkFeaturePresent() {
- boolean featurePresent = mService == null;
+ boolean featurePresent = mService != null;
if (!featurePresent && DEBUG) {
Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP
+ " not available");
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index c28172c..f1c2f34 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -150,7 +150,7 @@
public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
synchronized (mPrimaryClipChangedListeners) {
- if (mPrimaryClipChangedListeners.size() == 0) {
+ if (mPrimaryClipChangedListeners.isEmpty()) {
try {
mService.addPrimaryClipChangedListener(
mPrimaryClipChangedServiceListener, mContext.getOpPackageName());
@@ -165,7 +165,7 @@
public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
synchronized (mPrimaryClipChangedListeners) {
mPrimaryClipChangedListeners.remove(what);
- if (mPrimaryClipChangedListeners.size() == 0) {
+ if (mPrimaryClipChangedListeners.isEmpty()) {
try {
mService.removePrimaryClipChangedListener(
mPrimaryClipChangedServiceListener);
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index fd1e24a..8f3a317 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -508,14 +508,14 @@
/** Create a ContentProviderOperation from this {@link Builder}. */
public ContentProviderOperation build() {
if (mType == TYPE_UPDATE) {
- if ((mValues == null || mValues.size() == 0)
- && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)) {
+ if ((mValues == null || mValues.isEmpty())
+ && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())) {
throw new IllegalArgumentException("Empty values");
}
}
if (mType == TYPE_ASSERT) {
- if ((mValues == null || mValues.size() == 0)
- && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)
+ if ((mValues == null || mValues.isEmpty())
+ && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())
&& (mExpectedCount == null)) {
throw new IllegalArgumentException("Empty values");
}
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 3a87cb3..6f93798 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -204,6 +204,17 @@
}
/**
+ * Indicates whether this collection is empty.
+ *
+ * @return true iff size == 0
+ * {@hide}
+ * TODO: consider exposing this new method publicly
+ */
+ public boolean isEmpty() {
+ return mValues.isEmpty();
+ }
+
+ /**
* Remove a single value.
*
* @param key the name of the value to remove
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 6dbe5fb..b01e6a1 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -223,6 +223,15 @@
public int resizeMode = RESIZE_MODE_RESIZEABLE;
/**
+ * Value indicating the maximum aspect ratio the activity supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#maxAspectRatio}.
+ * @hide
+ */
+ public float maxAspectRatio;
+
+ /**
* Name of the VrListenerService component to run for this activity.
* @see android.R.attr#enableVrMode
* @hide
@@ -922,6 +931,7 @@
requestedVrComponent = orig.requestedVrComponent;
rotationAnimation = orig.rotationAnimation;
colorMode = orig.colorMode;
+ maxAspectRatio = orig.maxAspectRatio;
}
/**
@@ -1064,6 +1074,9 @@
if (requestedVrComponent != null) {
pw.println(prefix + "requestedVrComponent=" + requestedVrComponent);
}
+ if (maxAspectRatio != 0) {
+ pw.println(prefix + "maxAspectRatio=" + maxAspectRatio);
+ }
super.dumpBack(pw, prefix, flags);
}
@@ -1110,6 +1123,7 @@
dest.writeString(requestedVrComponent);
dest.writeInt(rotationAnimation);
dest.writeInt(colorMode);
+ dest.writeFloat(maxAspectRatio);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1146,6 +1160,7 @@
requestedVrComponent = source.readString();
rotationAnimation = source.readInt();
colorMode = source.readInt();
+ maxAspectRatio = source.readFloat();
}
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ffc7719..939e20f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -603,6 +603,15 @@
public int largestWidthLimitDp = 0;
/**
+ * Value indicating the maximum aspect ratio the application supports.
+ * <p>
+ * 0 means unset.
+ * @See {@link android.R.attr#maxAspectRatio}.
+ * @hide
+ */
+ public float maxAspectRatio;
+
+ /**
* UUID of the storage volume on which this application is being hosted. For
* apps hosted on the default internal storage at
* {@link Environment#getDataDirectory()}, the UUID value is {@code null}.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fb69986..1fafe65 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -37,6 +37,7 @@
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
@@ -147,6 +148,8 @@
public static final int APK_SIGNING_V1 = 1;
public static final int APK_SIGNING_V2 = 2;
+ private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+
// TODO: switch outError users to PackageParserException
// TODO: refactor "codePath" to "apkPath"
@@ -3465,6 +3468,8 @@
ai.privateFlags |= PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
}
+ ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
+
ai.networkSecurityConfigRes = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig,
0);
@@ -4161,6 +4166,8 @@
a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
}
+ setActivityMaxAspectRatio(a.info, sa, owner);
+
a.info.lockTaskLaunchMode =
sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
@@ -4376,6 +4383,27 @@
}
}
+ private void setActivityMaxAspectRatio(ActivityInfo aInfo, TypedArray sa, Package owner) {
+ if (aInfo.resizeMode == RESIZE_MODE_RESIZEABLE
+ || aInfo.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+ // Resizeable activities can be put in any aspect ratio.
+ aInfo.maxAspectRatio = 0;
+ return;
+ }
+
+ // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
+ // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
+ float defaultMaxAspectRatio = owner.applicationInfo.targetSdkVersion < O
+ ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+ if (owner.applicationInfo.maxAspectRatio != 0 ) {
+ // Use the application max aspect ration as default if set.
+ defaultMaxAspectRatio = owner.applicationInfo.maxAspectRatio;
+ }
+
+ aInfo.maxAspectRatio = Math.max(1.0f, sa.getFloat(
+ R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio));
+ }
+
/**
* @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
* @param restartOnConfigChanges The bit mask restartOnConfigChanges fetched from
@@ -4512,6 +4540,7 @@
info.maxRecents = target.info.maxRecents;
info.windowLayout = target.info.windowLayout;
info.resizeMode = target.info.resizeMode;
+ info.maxAspectRatio = target.info.maxAspectRatio;
info.encryptionAware = info.directBootAware = target.info.directBootAware;
Activity a = new Activity(mParseActivityAliasArgs, info);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8e17832..fe849b8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1449,7 +1449,7 @@
sql.append('(');
Object[] bindArgs = null;
- int size = (initialValues != null && initialValues.size() > 0)
+ int size = (initialValues != null && !initialValues.isEmpty())
? initialValues.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
@@ -1541,7 +1541,7 @@
*/
public int updateWithOnConflict(String table, ContentValues values,
String whereClause, String[] whereArgs, int conflictAlgorithm) {
- if (values == null || values.size() == 0) {
+ if (values == null || values.isEmpty()) {
throw new IllegalArgumentException("Empty values");
}
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index f82c9e2..ad20ce5 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -130,9 +130,11 @@
continue;
}
- mKeyphrasePackageMap.put(
- getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors),
- ai.packageName);
+ KeyphraseMetadata metadata =
+ getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors);
+ if (metadata != null) {
+ mKeyphrasePackageMap.put(metadata, ai.packageName);
+ }
} catch (PackageManager.NameNotFoundException e) {
String error = "error parsing voice enrollment meta-data for "
+ ri.activityInfo.packageName;
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 60b27b4..0448221 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -16,6 +16,7 @@
package android.metrics;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.util.Log;
import android.util.SparseArray;
@@ -118,6 +119,16 @@
return this;
}
+ /**
+ * @param component to replace the existing setting.
+ * @hide
+ */
+ public LogMaker setComponentName(ComponentName component) {
+ entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName());
+ entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName());
+ return this;
+ }
+
/** Remove the package name property. */
public LogMaker clearPackageName() {
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 29884b1..dd11f68 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -24,14 +24,12 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Predicate;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.LongSparseArray;
import android.util.MutableBoolean;
import android.util.Pair;
@@ -184,7 +182,7 @@
* New in version 19:
* - Wakelock data (wl) gets current and max times.
* New in version 20:
- * - Sensor gets a background counter.
+ * - Sensor, BluetoothScan, WifiScan get background timers and counter.
*/
static final String CHECKIN_VERSION = "20";
@@ -363,7 +361,7 @@
/**
* Returns the max duration if it is being tracked.
- * Not all Timer subclasses track the max duration and the current duration.
+ * Not all Timer subclasses track the max, total, current durations.
*/
public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
@@ -372,13 +370,28 @@
/**
* Returns the current time the timer has been active, if it is being tracked.
- * Not all Timer subclasses track the max duration and the current duration.
+ * Not all Timer subclasses track the max, total, current durations.
*/
public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
return -1;
}
/**
+ * Returns the current time the timer has been active, if it is being tracked.
+ *
+ * Returns the total cumulative duration (i.e. sum of past durations) that this timer has
+ * been on since reset.
+ * This may differ from getTotalTimeLocked(elapsedRealtimeUs, STATS_SINCE_CHARGED)/1000 since,
+ * depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
+ * time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
+ * the actual total time.
+ * Not all Timer subclasses track the max, total, current durations.
+ */
+ public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+ return -1;
+ }
+
+ /**
* Returns whether the timer is currently running. Some types of timers
* (e.g. BatchTimers) don't know whether the event is currently active,
* and report false.
@@ -477,6 +490,9 @@
public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
public abstract int getWifiScanCount(int which);
+ public abstract int getWifiScanBackgroundCount(int which);
+ public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
+ public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
public abstract int getWifiBatchedScanCount(int csphBin, int which);
public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -486,6 +502,7 @@
public abstract Timer getCameraTurnedOnTimer();
public abstract Timer getForegroundActivityTimer();
public abstract Timer getBluetoothScanTimer();
+ public abstract Timer getBluetoothScanBackgroundTimer();
// Note: the following times are disjoint. They can be added together to find the
// total time a uid has had any processes running at all.
@@ -609,8 +626,8 @@
public abstract Timer getSensorTime();
- /** Returns a counter for usage count when in the background. */
- public abstract Counter getSensorBgCount();
+ /** Returns a Timer for sensor usage when app is in the background. */
+ public abstract Timer getSensorBackgroundTime();
}
public class Pid {
@@ -2652,7 +2669,7 @@
* @param pw a PrintWriter object to print to.
* @param sb a StringBuilder object.
* @param timer a Timer object contining the wakelock times.
- * @param rawRealtime the current on-battery time in microseconds.
+ * @param rawRealtimeUs the current on-battery time in microseconds.
* @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
* @param prefix a String to be prepended to each line of output.
* @param type the name of the timer.
@@ -3284,19 +3301,41 @@
final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
final int wifiScanCount = u.getWifiScanCount(which);
+ final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+ // Note that 'ActualTime' are unpooled and always since reset (regardless of 'which')
+ final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
+ final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+ || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
|| uidWifiRunningTime != 0) {
dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime,
uidWifiRunningTime, wifiScanCount,
- /* legacy fields follow, keep at 0 */ 0, 0, 0);
+ /* legacy fields follow, keep at 0 */ 0, 0, 0,
+ wifiScanCountBg, wifiScanActualTime, wifiScanActualTimeBg);
}
dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA,
u.getWifiControllerActivity(), which);
- dumpTimer(pw, uid, category, BLUETOOTH_MISC_DATA, u.getBluetoothScanTimer(),
- rawRealtime, which);
+ final Timer bleTimer = u.getBluetoothScanTimer();
+ if (bleTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ final long totalTime = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+ / 1000;
+ if (totalTime != 0) {
+ final int count = bleTimer.getCountLocked(which);
+ final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+ final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTime = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long actualTimeBg = bleTimerBg != null ?
+ bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ dumpLine(pw, uid, category, BLUETOOTH_MISC_DATA, totalTime, count,
+ countBg, actualTime, actualTimeBg);
+ }
+ }
dumpControllerActivityLine(pw, uid, category, BLUETOOTH_CONTROLLER_DATA,
u.getBluetoothControllerActivity(), which);
@@ -3375,16 +3414,21 @@
final Uid.Sensor se = sensors.valueAt(ise);
final int sensorNumber = sensors.keyAt(ise);
final Timer timer = se.getSensorTime();
- final Counter bgCounter = se.getSensorBgCount();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
/ 1000;
- final int count = timer.getCountLocked(which);
- final int bgCount = bgCounter != null ? bgCounter.getCountLocked(which) : 0;
if (totalTime != 0) {
- dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count,
- bgCount);
+ final int count = timer.getCountLocked(which);
+ final Timer bgTimer = se.getSensorBackgroundTime();
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long bgActualTime = bgTimer != null ?
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime,
+ count, bgCount, actualTime, bgActualTime);
}
}
}
@@ -4294,6 +4338,10 @@
final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
final int wifiScanCount = u.getWifiScanCount(which);
+ final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
+ final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
final long mobileWakeup = u.getMobileRadioApWakeupCount(which);
@@ -4344,6 +4392,7 @@
}
if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+ || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
|| uidWifiRunningTime != 0) {
sb.setLength(0);
sb.append(prefix); sb.append(" Wifi Running: ");
@@ -4354,11 +4403,26 @@
formatTimeMs(sb, fullWifiLockOnTime / 1000);
sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
whichBatteryRealtime)); sb.append(")\n");
- sb.append(prefix); sb.append(" Wifi Scan: ");
+ sb.append(prefix); sb.append(" Wifi Scan (blamed): ");
formatTimeMs(sb, wifiScanTime / 1000);
sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
whichBatteryRealtime)); sb.append(") ");
sb.append(wifiScanCount);
+ sb.append("x\n");
+ // actual and background times are unpooled and since reset (regardless of 'which')
+ sb.append(prefix); sb.append(" Wifi Scan (actual): ");
+ formatTimeMs(sb, wifiScanActualTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(wifiScanActualTime,
+ computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+ sb.append(") ");
+ sb.append(wifiScanCount);
+ sb.append("x\n");
+ sb.append(prefix); sb.append(" Background Wifi Scan: ");
+ formatTimeMs(sb, wifiScanActualTimeBg / 1000);
+ sb.append("("); sb.append(formatRatioLocked(wifiScanActualTimeBg,
+ computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+ sb.append(") ");
+ sb.append(wifiScanCountBg);
sb.append("x");
pw.println(sb.toString());
}
@@ -4381,8 +4445,50 @@
pw.println(" sent");
}
- uidActivity |= printTimer(pw, sb, u.getBluetoothScanTimer(), rawRealtime, which, prefix,
- "Bluetooth Scan");
+ final Timer bleTimer = u.getBluetoothScanTimer();
+ if (bleTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ final long totalTimeMs = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+ / 1000;
+ if (totalTimeMs != 0) {
+ final int count = bleTimer.getCountLocked(which);
+ final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+ final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTimeMs = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long actualTimeMsBg = bleTimerBg != null ?
+ bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append("Bluetooth Scan");
+ sb.append(": ");
+ if (actualTimeMs != totalTimeMs) {
+ formatTimeMs(sb, totalTimeMs);
+ sb.append("blamed realtime, ");
+ }
+ formatTimeMs(sb, actualTimeMs); // since reset, regardless of 'which'
+ sb.append("realtime (");
+ sb.append(count);
+ sb.append(" times)");
+ if (bleTimer.isRunningLocked()) {
+ sb.append(" (running)");
+ }
+ if (actualTimeMsBg != 0 || countBg > 0) {
+ sb.append(", ");
+ formatTimeMs(sb, actualTimeMsBg); // since reset, regardless of 'which'
+ sb.append("background (");
+ sb.append(countBg);
+ sb.append(" times)");
+ }
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
+
if (u.hasUserActivity()) {
boolean hasData = false;
@@ -4553,25 +4659,38 @@
sb.append(": ");
final Timer timer = se.getSensorTime();
- final Counter bgCounter = se.getSensorBgCount();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- final long totalTime = (timer.getTotalTimeLocked(
- rawRealtime, which) + 500) / 1000;
+ final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
+ / 1000;
final int count = timer.getCountLocked(which);
- final int bgCount = bgCounter != null ? bgCounter.getCountLocked(which) : 0;
+ final Timer bgTimer = se.getSensorBackgroundTime();
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ // 'actualTime' are unpooled and always since reset (regardless of 'which')
+ final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+ final long bgActualTime = bgTimer != null ?
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+
//timer.logState();
if (totalTime != 0) {
- formatTimeMs(sb, totalTime);
+ if (actualTime != totalTime) {
+ formatTimeMs(sb, totalTime);
+ sb.append("blamed realtime, ");
+ }
+
+ formatTimeMs(sb, actualTime); // since reset, regardless of 'which'
sb.append("realtime (");
sb.append(count);
- sb.append(" times");
- if (bgCount > 0) {
+ sb.append(" times)");
+
+ if (bgActualTime != 0 || bgCount > 0) {
sb.append(", ");
+ formatTimeMs(sb, bgActualTime); // since reset, regardless of 'which'
+ sb.append("background (");
sb.append(bgCount);
- sb.append(" bg");
+ sb.append(" times)");
}
- sb.append(")");
} else {
sb.append("(not used)");
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2e35a51..76128e6 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2039,14 +2039,20 @@
}
}
+ /** @deprecated use {@link android.system.Os#open(String, int, int)} */
+ @Deprecated
+ static native FileDescriptor openFileDescriptor(String file, int mode)
+ throws FileNotFoundException;
- /*package*/ static native FileDescriptor openFileDescriptor(String file,
- int mode) throws FileNotFoundException;
- /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
- throws IOException;
- /*package*/ static native void closeFileDescriptor(FileDescriptor desc)
- throws IOException;
- /*package*/ static native void clearFileDescriptor(FileDescriptor desc);
+ /** @deprecated use {@link android.system.Os#dup(FileDescriptor)} */
+ @Deprecated
+ static native FileDescriptor dupFileDescriptor(FileDescriptor orig) throws IOException;
+
+ /** @deprecated use {@link android.system.Os#close(FileDescriptor)} */
+ @Deprecated
+ static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
+
+ static native void clearFileDescriptor(FileDescriptor desc);
/**
* Read a byte value from the parcel at the current dataPosition().
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 8882672..3212139 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -17,11 +17,21 @@
package android.os;
import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
import static android.system.OsConstants.SEEK_SET;
-import static android.system.OsConstants.SOCK_STREAM;
import static android.system.OsConstants.SOCK_SEQPACKET;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXU;
import static android.system.OsConstants.S_ISLNK;
import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_IWOTH;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
@@ -33,6 +43,7 @@
import android.util.Log;
import dalvik.system.CloseGuard;
+
import libcore.io.IoUtils;
import libcore.io.Memory;
@@ -279,8 +290,28 @@
"Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
}
+ int flags = 0;
+ switch (mode & MODE_READ_WRITE) {
+ case 0:
+ case MODE_READ_ONLY: flags = O_RDONLY; break;
+ case MODE_WRITE_ONLY: flags = O_WRONLY; break;
+ case MODE_READ_WRITE: flags = O_RDWR; break;
+ }
+
+ if ((mode & MODE_CREATE) != 0) flags |= O_CREAT;
+ if ((mode & MODE_TRUNCATE) != 0) flags |= O_TRUNC;
+ if ((mode & MODE_APPEND) != 0) flags |= O_APPEND;
+
+ int realMode = S_IRWXU | S_IRWXG;
+ if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
+ if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
+
final String path = file.getPath();
- return Parcel.openFileDescriptor(path, mode);
+ try {
+ return Os.open(path, flags, realMode);
+ } catch (ErrnoException e) {
+ throw new FileNotFoundException(e.getMessage());
+ }
}
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 53c9a23..7e1b5ab 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -113,6 +113,8 @@
public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
/** {@hide} */
public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
+ /** {@hide} */
+ public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
/** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
@@ -140,6 +142,8 @@
public static final int DEBUG_SDCARDFS_FORCE_ON = 1 << 2;
/** {@hide} */
public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 3;
+ /** {@hide} */
+ public static final int DEBUG_VIRTUAL_DISK = 1 << 4;
// NOTE: keep in sync with installd
/** {@hide} */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8352874..7005d44 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5482,6 +5482,20 @@
public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
/**
+ * Setting specifying if the accessibility shortcut is enabled.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_ENABLED =
+ "accessibility_shortcut_enabled";
+
+ /**
+ * Setting specifying if the accessibility shortcut is enabled.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN =
+ "accessibility_shortcut_on_lock_screen";
+
+ /**
* Setting specifying if the accessibility shortcut dialog has been shown to this user.
* @hide
*/
@@ -5489,7 +5503,7 @@
"accessibility_shortcut_dialog_shown";
/**
- * Setting specifying the the accessibility service to be toggled via the accessibility
+ * Setting specifying the accessibility service to be toggled via the accessibility
* shortcut. Must be its flattened {@link ComponentName}.
* @hide
*/
@@ -5497,6 +5511,16 @@
"accessibility_shortcut_target_service";
/**
+ * Setting specifying the accessibility service or feature to be toggled via the
+ * accessibility button in the navigation bar. This is either a flattened
+ * {@link ComponentName} or the class name of a system class implementing a supported
+ * accessibility feature.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_BUTTON_TARGET_COMPONENT =
+ "accessibility_button_target_component";
+
+ /**
* If touch exploration is enabled.
*/
public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
@@ -6983,7 +7007,10 @@
TOUCH_EXPLORATION_ENABLED,
ACCESSIBILITY_ENABLED,
ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ ACCESSIBILITY_SHORTCUT_ENABLED,
+ ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
ACCESSIBILITY_SPEAK_PASSWORD,
ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
ACCESSIBILITY_CAPTIONING_PRESET,
@@ -9731,6 +9758,15 @@
public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants";
/**
+ * Indicates the maximum time that an app is blocked for the network rules to get updated.
+ *
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms";
+
+ /**
* The reason for the settings database being downgraded. This is only for
* troubleshooting purposes and its value should not be interpreted in any way.
*
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index c9f9f31..35276cc 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -22,6 +22,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SdkConstant;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -97,6 +98,22 @@
/** @hide */
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+ /**
+ * Activity action: Launch UI to manage which accessibility service or feature is assigned
+ * to the navigation bar Accessibility button.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+ "android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
static final Object sInstanceSync = new Object();
private static AccessibilityManager sInstance;
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 9ed6371..b4d2c6b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,14 +26,20 @@
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.Rect;
+import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import android.view.View;
import android.view.WindowManagerGlobal;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -83,7 +89,7 @@
/** @hide */ public static final int FLAG_VIEW_EXITED = 0x20000000;
/** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000;
- @NonNull private final Rect mTempRect = new Rect();
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final IAutoFillManager mService;
private IAutoFillManagerClient mServiceClient;
@@ -98,25 +104,37 @@
/** @hide */
public interface AutofillClient {
/**
- * Asks the client to perform an autofill.
- *
- * @param ids The values to autofill
- * @param values The values to autofill
- */
- void autofill(List<AutofillId> ids, List<AutofillValue> values);
-
- /**
* Asks the client to start an authentication flow.
*
* @param intent The authentication intent.
* @param fillInIntent The authentication fill-in intent.
*/
- void authenticate(IntentSender intent, Intent fillInIntent);
+ void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent);
/**
* Tells the client this manager has state to be reset.
*/
- void resetableStateAvailable();
+ void autofillCallbackResetableStateAvailable();
+
+ /**
+ * Request showing the autofill UI.
+ *
+ * @param anchor The real view the UI needs to anchor to.
+ * @param width The width of the fill UI content.
+ * @param height The height of the fill UI content.
+ * @param virtualBounds The bounds of the virtual decendant of the anchor.
+ * @param presenter The presenter that controls the fill UI window.
+ * @return Whether the UI was shown.
+ */
+ boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
+ @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
+
+ /**
+ * Request hiding the autofill UI.
+ *
+ * @return Whether the UI was hidden.
+ */
+ boolean autofillCallbackRequestHideFillUi();
}
/**
@@ -156,12 +174,10 @@
return;
}
- final Rect bounds = mTempRect;
- view.getBoundsOnScreen(bounds);
final AutofillId id = getAutofillId(view);
final AutofillValue value = view.getAutofillValue();
- startSession(id, view.getWindowToken(), bounds, value, FLAG_MANUAL_REQUEST);
+ startSession(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST);
}
/**
@@ -202,17 +218,15 @@
return;
}
- final Rect bounds = mTempRect;
- view.getBoundsOnScreen(bounds);
final AutofillId id = getAutofillId(view);
final AutofillValue value = view.getAutofillValue();
if (!mHasSession) {
// Starts new session.
- startSession(id, view.getWindowToken(), bounds, value, 0);
+ startSession(id, view.getWindowToken(), null, value, 0);
} else {
// Update focus on existing session.
- updateSession(id, bounds, value, FLAG_VIEW_ENTERED);
+ updateSession(id, null, value, FLAG_VIEW_ENTERED);
}
}
@@ -389,7 +403,7 @@
mCallback != null, flags, mContext.getOpPackageName());
AutofillClient client = getClient();
if (client != null) {
- client.resetableStateAvailable();
+ client.autofillCallbackResetableStateAvailable();
}
mHasSession = true;
} catch (RemoteException e) {
@@ -490,28 +504,114 @@
}
}
- private void onAutofillEvent(IBinder windowToken, AutofillId id, int event) {
- if (mCallback == null) return;
- if (id == null) {
- Log.w(TAG, "onAutofillEvent(): no id for event " + event);
+ private void requestShowFillUi(IBinder windowToken, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final View anchor = findAchorView(windowToken, id);
+ if (anchor == null) {
+ return;
+ }
+ if (getClient().autofillCallbackRequestShowFillUi(anchor, width, height,
+ anchorBounds, presenter) && mCallback != null) {
+ if (id.isVirtual()) {
+ mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_SHOWN);
+ } else {
+ mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
+ }
+ }
+ }
+
+ private void handleAutofill(IBinder windowToken, List<AutofillId> ids,
+ List<AutofillValue> values) {
+ final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
+ if (root == null) {
return;
}
+ final int itemCount = ids.size();
+ int numApplied = 0;
+ ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
+
+ for (int i = 0; i < itemCount; i++) {
+ final AutofillId id = ids.get(i);
+ final AutofillValue value = values.get(i);
+ final int viewId = id.getViewId();
+ final View view = root.findViewByAccessibilityIdTraversal(viewId);
+ if (view == null) {
+ Log.w(TAG, "autofill(): no View with id " + viewId);
+ continue;
+ }
+ if (id.isVirtual()) {
+ if (virtualValues == null) {
+ // Most likely there will be just one view with virtual children.
+ virtualValues = new ArrayMap<>(1);
+ }
+ SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+ if (valuesByParent == null) {
+ // We don't know the size yet, but usually it will be just a few fields...
+ valuesByParent = new SparseArray<>(5);
+ virtualValues.put(view, valuesByParent);
+ }
+ valuesByParent.put(id.getVirtualChildId(), value);
+ } else {
+ if (view.autofill(value)) {
+ numApplied++;
+ }
+ }
+ }
+
+ if (virtualValues != null) {
+ for (int i = 0; i < virtualValues.size(); i++) {
+ final View parent = virtualValues.keyAt(i);
+ final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+ if (parent.autofill(childrenValues)) {
+ numApplied += childrenValues.size();
+ }
+ }
+ }
+
+ final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
+ log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
+ log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
+ mMetricsLogger.write(log);
+ }
+
+ private void requestHideFillUi(IBinder windowToken, AutofillId id) {
+ if (getClient().autofillCallbackRequestHideFillUi() && mCallback != null) {
+ final View anchor = findAchorView(windowToken, id);
+ if (id.isVirtual()) {
+ mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_HIDDEN);
+ } else {
+ mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
+ }
+ }
+ }
+
+ private void notifyNoFillUi(IBinder windowToken, AutofillId id) {
+ if (mCallback != null) {
+ final View anchor = findAchorView(windowToken, id);
+ if (id.isVirtual()) {
+ mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ } else {
+ mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ }
+ }
+ }
+
+ private View findAchorView(IBinder windowToken, AutofillId id) {
final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
if (root == null) {
- Log.w(TAG, "onAutofillEvent() for " + id + ": root view gone");
- return;
+ Log.w(TAG, "no window with token " + windowToken);
+ return null;
}
final View view = root.findViewByAccessibilityIdTraversal(id.getViewId());
if (view == null) {
- Log.w(TAG, "onAutofillEvent() for " + id + ": view gone");
- return;
+ Log.w(TAG, "no view with id " + id);
+ return null;
}
- if (id.isVirtual()) {
- mCallback.onAutofillEvent(view, id.getVirtualChildId(), event);
- } else {
- mCallback.onAutofillEvent(view, event);
- }
+ return view;
}
/**
@@ -590,16 +690,14 @@
}
@Override
- public void autofill(List<AutofillId> ids, List<AutofillValue> values) {
+ public void autofill(IBinder windowToken, List<AutofillId> ids,
+ List<AutofillValue> values) {
// TODO(b/33197203): must keep the dataset so subsequent calls pass the same
// dataset.extras to service
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.mContext.getMainThreadHandler().post(() -> {
- if (afm.getClient() != null) {
- afm.getClient().autofill(ids, values);
- }
- });
+ afm.mContext.getMainThreadHandler().post(() ->
+ afm.handleAutofill(windowToken, ids, values));
}
}
@@ -609,19 +707,45 @@
if (afm != null) {
afm.mContext.getMainThreadHandler().post(() -> {
if (afm.getClient() != null) {
- afm.getClient().authenticate(intent, fillInIntent);
+ afm.getClient().autofillCallbackAuthenticate(intent, fillInIntent);
}
});
}
}
@Override
- public void onAutofillEvent(IBinder windowToken, AutofillId id, int event) {
+ public void requestShowFillUi(IBinder windowToken, AutofillId id,
+ int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
afm.mContext.getMainThreadHandler().post(() -> {
if (afm.getClient() != null) {
- afm.onAutofillEvent(windowToken, id, event);
+ afm.requestShowFillUi(windowToken, id, width,
+ height, anchorBounds, presenter);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void requestHideFillUi(IBinder windowToken, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.mContext.getMainThreadHandler().post(() -> {
+ if (afm.getClient() != null) {
+ afm.requestHideFillUi(windowToken, id);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void notifyNoFillUi(IBinder windowToken, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.mContext.getMainThreadHandler().post(() -> {
+ if (afm.getClient() != null) {
+ afm.notifyNoFillUi(windowToken, id);
}
});
}
diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java
new file mode 100644
index 0000000..056e540
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillPopupWindow.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.transition.Transition;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.PopupWindow;
+
+/**
+ * Custom {@link PopupWindow} used to isolate its content from the autofilled app - the
+ * UI is rendered in a framework process, but it's controlled by the app.
+ *
+ * TODO(b/34943932): use an app surface control solution.
+ *
+ * @hide
+ */
+public class AutofillPopupWindow extends PopupWindow {
+
+ private static final String TAG = "AutofillPopupWindow";
+
+ private final WindowPresenter mWindowPresenter;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ /**
+ * Creates a popup window with a presenter owning the window and responsible for
+ * showing/hiding/updating the backing window. This can be useful of the window is
+ * being shown by another process while the popup logic is in the process hosting
+ * the anchor view.
+ * <p>
+ * Using this constructor means that the presenter completely owns the content of
+ * the window and the following methods manipulating the window content shouldn't
+ * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)},
+ * {@link #getExitTransition()}, {@link #setExitTransition(Transition)},
+ * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()},
+ * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()},
+ * {@link #setElevation(float)}, ({@link #getAnimationStyle()},
+ * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p>
+ */
+ public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
+ mWindowPresenter = new WindowPresenter(presenter);
+
+ setOutsideTouchable(true);
+ setInputMethodMode(INPUT_METHOD_NEEDED);
+ }
+
+ @Override
+ protected boolean hasContentView() {
+ return true;
+ }
+
+ @Override
+ protected boolean hasDecorView() {
+ return true;
+ }
+
+ @Override
+ protected LayoutParams getDecorViewLayoutParams() {
+ return mWindowLayoutParams;
+ }
+
+ /**
+ * The effective {@code update} method that should be called by its clients.
+ */
+ public void update(View anchor, int offsetX, int offsetY, int width, int height,
+ Rect anchorBounds, Rect actualAnchorBounds) {
+ if (!isShowing()) {
+ setWidth(width);
+ setHeight(height);
+ showAsDropDown(anchor, offsetX, offsetY);
+ } else {
+ update(anchor, offsetX, offsetY, width, height);
+ }
+
+ if (anchorBounds != null && mWindowLayoutParams.y > anchorBounds.bottom) {
+ offsetY = anchorBounds.bottom - actualAnchorBounds.bottom;
+ update(anchor, offsetX, offsetY, width, height);
+ }
+ }
+
+ @Override
+ protected void update(View anchor, WindowManager.LayoutParams params) {
+ final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
+ : View.LAYOUT_DIRECTION_LOCALE;
+ mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
+ layoutDirection);
+ }
+
+ @Override
+ public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
+ if (isShowing()) {
+ return;
+ }
+
+ setShowing(true);
+ setDropDown(true);
+ attachToAnchor(anchor, xoff, yoff, gravity);
+ final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
+ anchor.getWindowToken());
+ final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
+ p.width, p.height, gravity, getAllowScrollingAnchorParent());
+ updateAboveAnchor(aboveAnchor);
+ p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+ p.packageName = anchor.getContext().getPackageName();
+ mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
+ anchor.getLayoutDirection());
+ return;
+ }
+
+ @Override
+ public void dismiss() {
+ if (!isShowing() || isTransitioningToDismiss()) {
+ return;
+ }
+
+ setShowing(false);
+ setTransitioningToDismiss(true);
+
+ mWindowPresenter.hide(getTransitionEpicenter());
+ detachFromAnchor();
+ if (getOnDismissListener() != null) {
+ getOnDismissListener().onDismiss();
+ }
+ }
+
+ @Override
+ public int getAnimationStyle() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Drawable getBackground() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public View getContentView() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public float getElevation() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Transition getEnterTransition() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Transition getExitTransition() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setAnimationStyle(int animationStyle) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setBackgroundDrawable(Drawable background) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setContentView(View contentView) {
+ if (contentView != null) {
+ throw new IllegalStateException("You can't call this!");
+ }
+ }
+
+ @Override
+ public void setElevation(float elevation) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setEnterTransition(Transition enterTransition) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setExitTransition(Transition exitTransition) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setTouchInterceptor(OnTouchListener l) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ /**
+ * Contract between the popup window and a presenter that is responsible for
+ * showing/hiding/updating the actual window.
+ *
+ * <p>This can be useful if the anchor is in one process and the backing window is owned by
+ * another process.
+ */
+ private class WindowPresenter {
+ final IAutofillWindowPresenter mPresenter;
+
+ WindowPresenter(IAutofillWindowPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ /**
+ * Shows the backing window.
+ *
+ * @param p The window layout params.
+ * @param transitionEpicenter The transition epicenter if animating.
+ * @param fitsSystemWindows Whether the content view should account for system decorations.
+ * @param layoutDirection The content layout direction to be consistent with the anchor.
+ */
+ void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
+ int layoutDirection) {
+ try {
+ mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error showing fill window", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Hides the backing window.
+ *
+ * @param transitionEpicenter The transition epicenter if animating.
+ */
+ void hide(Rect transitionEpicenter) {
+ try {
+ mPresenter.hide(transitionEpicenter);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error hiding fill window", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index eabf6b1..7bea174 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -20,9 +20,11 @@
import android.content.Intent;
import android.content.IntentSender;
+import android.graphics.Rect;
import android.os.IBinder;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
/**
* Object running in the application process and responsible for autofilling it.
@@ -38,7 +40,7 @@
/**
* Autofills the activity with the contents of a dataset.
*/
- void autofill(in List<AutofillId> ids, in List<AutofillValue> values);
+ void autofill(in IBinder windowToken, in List<AutofillId> ids, in List<AutofillValue> values);
/**
* Authenticates a fill response or a data set.
@@ -46,7 +48,18 @@
void authenticate(in IntentSender intent, in Intent fillInIntent);
/**
- * Notifies the client when the auto-fill UI changed.
+ * Requests showing the fill UI.
*/
- void onAutofillEvent(in IBinder windowToken, in AutofillId id, int event);
+ void requestShowFillUi(in IBinder windowToken, in AutofillId id, int width,
+ int height, in Rect anchorBounds, in IAutofillWindowPresenter presenter);
+
+ /**
+ * Requests hiding the fill UI.
+ */
+ void requestHideFillUi(in IBinder windowToken, in AutofillId id);
+
+ /**
+ * Nitifies no fill UI will be shown.
+ */
+ void notifyNoFillUi(in IBinder windowToken, in AutofillId id);
}
diff --git a/core/java/android/view/autofill/IAutofillWindowPresenter.aidl b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl
new file mode 100644
index 0000000..172b992
--- /dev/null
+++ b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+/**
+ * This is a handle to the FillUi for controlling
+ * when its window should be shown and hidden.
+ *
+ * {@hide}
+ */
+oneway interface IAutofillWindowPresenter {
+ void show(in WindowManager.LayoutParams p, in Rect transitionEpicenter,
+ boolean fitsSystemWindows, int layoutDirection);
+ void hide(in Rect transitionEpicenter);
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 46f7a81..dabbf31 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -70,26 +70,6 @@
public LinksInfo getLinks(CharSequence text, int linkMask, LocaleList defaultLocales) {
return LinksInfo.NO_OP;
}
-
- // TODO: Remove
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public LinksInfo getLinks(CharSequence text, int linkMask) {
- throw new UnsupportedOperationException("Removed");
- }
};
/**
@@ -154,16 +134,4 @@
*/
LinksInfo getLinks(
@NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales);
-
- // TODO: Remove
- /** @removed */
- TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex);
- // TODO: Remove
- /** @removed */
- TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex);
- // TODO: Remove
- /** @removed */
- LinksInfo getLinks(CharSequence text, int linkMask);
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 06ac869..be12f57 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -120,10 +120,12 @@
SmartSelection.ClassificationResult[] results = getSmartSelection()
.classifyText(text.toString(), startIndex, endIndex);
if (results.length > 0) {
+ final TextClassificationResult classificationResult =
+ createClassificationResult(results, classified);
// TODO: Added this log for debug only. Remove before release.
Log.d(LOG_TAG, String.format(
- "Classification type: %s", getHighestScoringType(results)));
- return createClassificationResult(results, classified);
+ "Classification type: %s", classificationResult));
+ return classificationResult;
}
}
} catch (Throwable t) {
@@ -149,26 +151,6 @@
return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
}
- // TODO: Remove
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public LinksInfo getLinks(CharSequence text, int linkMask) {
- throw new UnsupportedOperationException("Removed");
- }
-
private SmartSelection getSmartSelection() throws FileNotFoundException {
synchronized (mSmartSelectionLock) {
if (mSmartSelection == null) {
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index fae5742..8f662ba 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -498,18 +498,30 @@
public void onProvideAutofillStructure(ViewStructure structure, int flags) {
super.onProvideAutofillStructure(structure, flags);
- if (getAdapter() == null) return;
+ final SpinnerAdapter adapter = getAdapter();
+
+ if (adapter == null) return;
// TODO(b/33197203): implement sanitization so initial value is only sanitized when coming
// from resources.
- final int count = getAdapter().getCount();
+ final int count = adapter.getCount();
+ int size = 0;
if (count > 0) {
final String[] options = new String[count];
for (int i = 0; i < count; i++) {
- options[i] = getAdapter().getItem(i).toString();
+ final Object item = adapter.getItem(i);
+ if (item != null) {
+ options[size++] = item.toString();
+ }
}
- structure.setAutofillOptions(options);
+ if (size == count) {
+ structure.setAutofillOptions(options);
+ } else {
+ final String[] validOptions = new String[size];
+ System.arraycopy(options, 0, validOptions, 0, size);
+ structure.setAutofillOptions(validOptions);
+ }
}
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 9f10531..7d243af 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -209,7 +209,7 @@
private int mGravity = Gravity.NO_GRAVITY;
private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
- com.android.internal.R.attr.state_above_anchor
+ com.android.internal.R.attr.state_above_anchor
};
private final OnAttachStateChangeListener mOnAnchorDetachedListener =
@@ -589,7 +589,6 @@
mIgnoreCheekPress = true;
}
-
/**
* <p>Change the animation style resource for this popup.</p>
*
@@ -860,6 +859,11 @@
mAllowScrollingAnchorParent = enabled;
}
+ /** @hide */
+ protected final boolean getAllowScrollingAnchorParent() {
+ return mAllowScrollingAnchorParent;
+ }
+
/**
* <p>Indicates whether the popup window supports splitting touches.</p>
*
@@ -960,6 +964,11 @@
mLayoutInsetDecor = enabled;
}
+ /** @hide */
+ protected final boolean isLayoutInsetDecor() {
+ return mLayoutInsetDecor;
+ }
+
/**
* Set the layout type for this window.
* <p>
@@ -1122,6 +1131,26 @@
return mIsShowing;
}
+ /** @hide */
+ protected final void setShowing(boolean isShowing) {
+ mIsShowing = isShowing;
+ }
+
+ /** @hide */
+ protected final void setDropDown(boolean isDropDown) {
+ mIsDropdown = isDropDown;
+ }
+
+ /** @hide */
+ protected final void setTransitioningToDismiss(boolean transitioningToDismiss) {
+ mIsTransitioningToDismiss = transitioningToDismiss;
+ }
+
+ /** @hide */
+ protected final boolean isTransitioningToDismiss() {
+ return mIsTransitioningToDismiss;
+ }
+
/**
* <p>
* Display the content view in a popup window at the specified location. If the popup window
@@ -1232,7 +1261,7 @@
* @see #dismiss()
*/
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
- if (isShowing() || mContentView == null) {
+ if (isShowing() || !hasContentView()) {
return;
}
@@ -1255,7 +1284,8 @@
invokePopup(p);
}
- private void updateAboveAnchor(boolean aboveAnchor) {
+ /** @hide */
+ protected final void updateAboveAnchor(boolean aboveAnchor) {
if (aboveAnchor != mAboveAnchor) {
mAboveAnchor = aboveAnchor;
@@ -1426,8 +1456,10 @@
* @param token the window token used to bind the popup's window
*
* @return the layout parameters to pass to the window manager
+ *
+ * @hide
*/
- private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
+ protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// These gravity settings put the view at the top left corner of the
@@ -1510,7 +1542,7 @@
curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
if (mAttachedInDecor) {
- curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
+ curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
}
return curFlags;
}
@@ -1543,8 +1575,10 @@
* @param allowScroll whether the anchor view's parent may be scrolled
* when the popup window doesn't fit on screen
* @return true if the popup is translated upwards to fit on screen
+ *
+ * @hide
*/
- private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
+ protected final boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
final int anchorHeight = anchor.getHeight();
final int anchorWidth = anchor.getWidth();
@@ -1853,7 +1887,7 @@
* @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
- if (!isShowing() || mIsTransitioningToDismiss) {
+ if (!isShowing() || isTransitioningToDismiss()) {
return;
}
@@ -1923,8 +1957,10 @@
*
* @return the window-relative epicenter bounds to be used by enter and
* exit transitions
+ *
+ * @hide
*/
- private Rect getTransitionEpicenter() {
+ protected final Rect getTransitionEpicenter() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
final View decor = mDecorView;
if (anchor == null || decor == null) {
@@ -1981,6 +2017,11 @@
mOnDismissListener = onDismissListener;
}
+ /** @hide */
+ protected final OnDismissListener getOnDismissListener() {
+ return mOnDismissListener;
+ }
+
/**
* Updates the state of the popup window, if it is currently being displayed,
* from the currently set state.
@@ -1996,12 +2037,11 @@
* </ul>
*/
public void update() {
- if (!isShowing() || mContentView == null) {
+ if (!isShowing() || !hasContentView()) {
return;
}
- final WindowManager.LayoutParams p =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
boolean update = false;
@@ -2024,11 +2064,16 @@
}
if (update) {
- setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mDecorView, p);
+ update(mAnchor.get(), p);
}
}
+ /** @hide */
+ protected void update(View anchor, WindowManager.LayoutParams params) {
+ setLayoutDirectionFromAnchor();
+ mWindowManager.updateViewLayout(mDecorView, params);
+ }
+
/**
* Updates the dimension of the popup window.
* <p>
@@ -2039,8 +2084,7 @@
* @param height the new height in pixels, must be >= 0 or -1 to ignore
*/
public void update(int width, int height) {
- final WindowManager.LayoutParams p =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
update(p.x, p.y, width, height, false);
}
@@ -2086,12 +2130,11 @@
setHeight(height);
}
- if (!isShowing() || mContentView == null) {
+ if (!isShowing() || !hasContentView()) {
return;
}
- final WindowManager.LayoutParams p =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
boolean update = force;
@@ -2135,19 +2178,39 @@
update = true;
}
- int newAccessibilityIdOfAnchor =
- (mAnchor != null) ? mAnchor.get().getAccessibilityViewId() : -1;
+ View anchor = null;
+ int newAccessibilityIdOfAnchor = -1;
+
+ if (mAnchor != null && mAnchor.get() != null) {
+ anchor = mAnchor.get();
+ newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+ }
+
if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
update = true;
}
if (update) {
- setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mDecorView, p);
+ update(anchor, p);
}
}
+ /** @hide */
+ protected boolean hasContentView() {
+ return mContentView != null;
+ }
+
+ /** @hide */
+ protected boolean hasDecorView() {
+ return mDecorView != null;
+ }
+
+ /** @hide */
+ protected WindowManager.LayoutParams getDecorViewLayoutParams() {
+ return (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ }
+
/**
* Updates the position and the dimension of the popup window.
* <p>
@@ -2185,7 +2248,7 @@
private void update(View anchor, boolean updateLocation, int xoff, int yoff,
int width, int height) {
- if (!isShowing() || mContentView == null) {
+ if (!isShowing() || !hasContentView()) {
return;
}
@@ -2201,7 +2264,7 @@
mAnchorYoff = yoff;
}
- final LayoutParams p = (LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
final int oldGravity = p.gravity;
final int oldWidth = p.width;
final int oldHeight = p.height;
@@ -2243,7 +2306,8 @@
public void onDismiss();
}
- private void detachFromAnchor() {
+ /** @hide */
+ protected final void detachFromAnchor() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
if (anchor != null) {
final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2262,7 +2326,8 @@
mIsAnchorRootAttached = false;
}
- private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+ /** @hide */
+ protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
detachFromAnchor();
final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2287,9 +2352,8 @@
private void alignToAnchor() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
- if (anchor != null && anchor.isAttachedToWindow() && mDecorView != null) {
- final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mDecorView.getLayoutParams();
+ if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
p.width, p.height, mAnchoredGravity, false));
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
new file mode 100644
index 0000000..ee5d339
--- /dev/null
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.app;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Activity used to display and persist a service or feature target for the Accessibility button.
+ */
+public class AccessibilityButtonChooserActivity extends Activity {
+
+ private static final String MAGNIFICATION_COMPONENT_ID =
+ "com.android.server.accessibility.MagnificationController";
+
+ private AccessibilityButtonTarget mMagnificationTarget = null;
+
+ private List<AccessibilityButtonTarget> mTargets = null;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_button_chooser);
+
+ String component = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+ if (TextUtils.isEmpty(component)) {
+ TextView prompt = (TextView) findViewById(R.id.accessibility_button_prompt);
+ prompt.setVisibility(View.VISIBLE);
+ }
+
+ mMagnificationTarget = new AccessibilityButtonTarget(this, MAGNIFICATION_COMPONENT_ID,
+ R.string.accessibility_magnification_chooser_text,
+ R.drawable.resolver_icon_placeholder);
+
+ mTargets = getServiceAccessibilityButtonTargets(this);
+ if (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
+ mTargets.add(mMagnificationTarget);
+ }
+
+ if (mTargets.size() < 2) {
+ // Why are we here?
+ finish();
+ }
+
+ GridView gridview = (GridView) findViewById(R.id.accessibility_button_chooser_grid);
+ gridview.setAdapter(new TargetAdapter());
+ gridview.setOnItemClickListener((parent, view, position, id) -> {
+ onTargetSelected(mTargets.get(position));
+ });
+ }
+
+ private static List<AccessibilityButtonTarget> getServiceAccessibilityButtonTargets(
+ @NonNull Context context) {
+ AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ List<AccessibilityServiceInfo> services = ams.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ if (services == null) {
+ return Collections.emptyList();
+ }
+
+ ArrayList<AccessibilityButtonTarget> targets = new ArrayList<>(services.size());
+ for (AccessibilityServiceInfo info : services) {
+ if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+ targets.add(new AccessibilityButtonTarget(context, info));
+ }
+ }
+
+ return targets;
+ }
+
+ private void onTargetSelected(AccessibilityButtonTarget target) {
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, target.getId());
+ finish();
+ }
+
+ private class TargetAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mTargets.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = AccessibilityButtonChooserActivity.this.getLayoutInflater();
+ View root = inflater.inflate(R.layout.accessibility_button_chooser_item, parent, false);
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ ImageView iconView = root.findViewById(R.id.accessibility_button_target_icon);
+ TextView labelView = root.findViewById(R.id.accessibility_button_target_label);
+ iconView.setImageDrawable(target.getDrawable());
+ labelView.setText(target.getLabel());
+ return root;
+ }
+ }
+
+ private static class AccessibilityButtonTarget {
+ public String mId;
+ public CharSequence mLabel;
+ public Drawable mDrawable;
+
+ public AccessibilityButtonTarget(@NonNull Context context,
+ @NonNull AccessibilityServiceInfo serviceInfo) {
+ this.mId = serviceInfo.getComponentName().flattenToString();
+ this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager());
+ this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager());
+ }
+
+ public AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId,
+ int iconRes) {
+ this.mId = id;
+ this.mLabel = context.getText(labelResId);
+ this.mDrawable = context.getDrawable(iconRes);
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ public Drawable getDrawable() {
+ return mDrawable;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9d2141d..3d0d6bf 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -482,7 +482,8 @@
new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS];
int mBluetoothScanNesting;
- StopwatchTimer mBluetoothScanTimer;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected StopwatchTimer mBluetoothScanTimer;
int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
long mMobileRadioActiveStartTime;
@@ -1589,19 +1590,28 @@
long mStartTimeMs = -1;
/**
- * The longest time period (in ms) that the timer has been active.
+ * The longest time period (in ms) that the timer has been active. Not pooled.
*/
long mMaxDurationMs;
/**
- * The total time (in ms) that that the timer has been active since reset().
+ * The time (in ms) that that the timer has been active since most recent
+ * stopRunningLocked() or reset(). Not pooled.
*/
long mCurrentDurationMs;
+ /**
+ * The total time (in ms) that that the timer has been active since most recent reset()
+ * prior to the current startRunningLocked. This is the sum of all past currentDurations
+ * (but not including the present currentDuration) since reset. Not pooled.
+ */
+ long mTotalDurationMs;
+
public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
TimeBase timeBase, Parcel in) {
super(clocks, uid, type, timerPool, timeBase, in);
mMaxDurationMs = in.readLong();
+ mTotalDurationMs = in.readLong();
}
public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
@@ -1613,6 +1623,7 @@
public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
super.writeToParcel(out, elapsedRealtimeUs);
out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+ out.writeLong(getTotalDurationMsLocked(elapsedRealtimeUs / 1000));
}
/**
@@ -1620,12 +1631,13 @@
*
* Since the time base is probably meaningless after we come back, reading
* from this will have the effect of stopping the timer. So here all we write
- * is the max duration.
+ * is the max and total durations.
*/
@Override
public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+ out.writeLong(getTotalDurationMsLocked(elapsedRealtimeUs / 1000));
}
/**
@@ -1637,6 +1649,7 @@
public void readSummaryFromParcelLocked(Parcel in) {
super.readSummaryFromParcelLocked(in);
mMaxDurationMs = in.readLong();
+ mTotalDurationMs = in.readLong();
mStartTimeMs = -1;
mCurrentDurationMs = 0;
}
@@ -1692,6 +1705,7 @@
public void stopRunningLocked(long elapsedRealtimeMs) {
if (mNesting == 1) {
final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs);
+ mTotalDurationMs += durationMs;
if (durationMs > mMaxDurationMs) {
mMaxDurationMs = durationMs;
}
@@ -1707,6 +1721,7 @@
public boolean reset(boolean detachIfReset) {
boolean result = super.reset(detachIfReset);
mMaxDurationMs = 0;
+ mTotalDurationMs = 0;
mCurrentDurationMs = 0;
if (mNesting > 0) {
mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000;
@@ -1735,6 +1750,7 @@
/**
* Returns the time since the timer was started.
+ * Returns 0 if the timer is not currently running.
*
* Note that this time is NOT split between the timers in the timer group that
* this timer is attached to. It is the TOTAL time.
@@ -1748,6 +1764,20 @@
}
return durationMs;
}
+
+ /**
+ * Returns the total cumulative duration that this timer has been on since reset().
+ * If mTimerPool == null, this should be the same
+ * as getTotalTimeLocked(elapsedRealtimeMs*1000, STATS_SINCE_CHARGED)/1000.
+ *
+ * Note that this time is NOT split between the timers in the timer group that
+ * this timer is attached to. It is the TOTAL time. For this reason, if mTimerPool != null,
+ * the result will not be equivalent to getTotalTimeLocked.
+ */
+ @Override
+ public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+ return mTotalDurationMs + getCurrentDurationMsLocked(elapsedRealtimeMs);
+ }
}
/**
@@ -1972,6 +2002,116 @@
}
}
+ /**
+ * State for keeping track of two DurationTimers with different TimeBases, presumably where one
+ * TimeBase is effectively a subset of the other.
+ */
+ public static class DualTimer {
+ // mMainTimer typically tracks the total time. May be pooled (but since it's a durationTimer,
+ // it also has the unpooled getTotalDurationMsLocked() for STATS_SINCE_CHARGED).
+ private final DurationTimer mMainTimer;
+ // mSubTimer typically tracks only part of the total time, such as background time, as
+ // determined by a subTimeBase. It is NOT pooled.
+ private final DurationTimer mSubTimer;
+
+ /**
+ * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
+ * The mMainTimer is based on the given timeBase and timerPool.
+ * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+ * the mMainTimer is.
+ */
+ public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+ TimeBase timeBase, TimeBase subTimeBase, Parcel in) {
+ mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase, in);
+ mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase, in);
+ }
+
+ /**
+ * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
+ * The mMainTimer is based on the given timeBase and timerPool.
+ * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+ * the mMainTimer is.
+ */
+ public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+ TimeBase timeBase, TimeBase subTimeBase) {
+ mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase);
+ mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase);
+ }
+
+ /** Get the main timer. */
+ public DurationTimer getMainTimer() {
+ return mMainTimer;
+ }
+
+ /** Get the secondary timer. */
+ public DurationTimer getSubTimer() {
+ return mSubTimer;
+ }
+
+ public void startRunningLocked(long elapsedRealtimeMs) {
+ mMainTimer.startRunningLocked(elapsedRealtimeMs);
+ mSubTimer.startRunningLocked(elapsedRealtimeMs);
+ }
+
+ public void stopRunningLocked(long elapsedRealtimeMs) {
+ mMainTimer.stopRunningLocked(elapsedRealtimeMs);
+ mSubTimer.stopRunningLocked(elapsedRealtimeMs);
+ }
+
+ public void stopAllRunningLocked(long elapsedRealtimeMs) {
+ mMainTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ mSubTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ }
+
+ public void setMark(long elapsedRealtimeMs) {
+ mMainTimer.setMark(elapsedRealtimeMs);
+ mSubTimer.setMark(elapsedRealtimeMs);
+ }
+
+ public boolean reset(boolean detachIfReset) {
+ boolean active = false;
+ active |= !mMainTimer.reset(detachIfReset);
+ active |= !mSubTimer.reset(detachIfReset);
+ return !active;
+ }
+
+ public void detach() {
+ mMainTimer.detach();
+ mSubTimer.detach();
+ }
+
+ /**
+ * Writes a possibly null DualTimer to a Parcel.
+ *
+ * @param out the Parcel to which to write.
+ * @param t a DualTimer, or null.
+ */
+ public static void writeDualTimerToParcel(Parcel out, DualTimer t, long elapsedRealtimeUs) {
+ if (t != null) {
+ out.writeInt(1);
+ t.writeToParcel(out, elapsedRealtimeUs);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ mMainTimer.writeToParcel(out, elapsedRealtimeUs);
+ mSubTimer.writeToParcel(out, elapsedRealtimeUs);
+ }
+
+ public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ mMainTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+ mSubTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+ }
+
+ public void readSummaryFromParcelLocked(Parcel in) {
+ mMainTimer.readSummaryFromParcelLocked(in);
+ mSubTimer.readSummaryFromParcelLocked(in);
+ }
+ }
+
+
public abstract class OverflowArrayMap<T> {
private static final String OVERFLOW_NAME = "*overflow*";
@@ -3149,7 +3289,13 @@
public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
long realtime) {
- mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
+ boolean batteryStatusChanged = mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
+
+ if (batteryStatusChanged) {
+ for (int i=0; i<mUidStats.size(); i++) {
+ mUidStats.valueAt(i).updateBgTimeBase(uptime, realtime);
+ }
+ }
boolean unpluggedScreenOff = unplugged && screenOff;
if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) {
@@ -4499,8 +4645,8 @@
private void noteBluetoothScanStartedLocked(int uid) {
uid = mapUid(uid);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
if (mBluetoothScanNesting == 0) {
mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
@@ -4521,8 +4667,8 @@
private void noteBluetoothScanStoppedLocked(int uid) {
uid = mapUid(uid);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
mBluetoothScanNesting--;
if (mBluetoothScanNesting == 0) {
mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
@@ -4543,8 +4689,8 @@
public void noteResetBluetoothScanLocked() {
if (mBluetoothScanNesting > 0) {
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
mBluetoothScanNesting = 0;
mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
@@ -5218,6 +5364,13 @@
return true;
}
+ private static boolean resetTimerIfNotNull(DualTimer timer, boolean detachIfReset) {
+ if (timer != null) {
+ return timer.reset(detachIfReset);
+ }
+ return true;
+ }
+
private static void detachLongCounterIfNotNull(LongSamplingCounter counter) {
if (counter != null) {
counter.detach();
@@ -5242,6 +5395,10 @@
final int mUid;
+ /** TimeBase for when uid is in background and device is on battery. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public final TimeBase mOnBatteryBackgroundTimeBase;
+
boolean mWifiRunning;
StopwatchTimer mWifiRunningTimer;
@@ -5249,7 +5406,7 @@
StopwatchTimer mFullWifiLockTimer;
boolean mWifiScanStarted;
- StopwatchTimer mWifiScanTimer;
+ DualTimer mWifiScanTimer;
static final int NO_BATCHED_SCAN_STARTED = -1;
int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
@@ -5263,7 +5420,7 @@
StopwatchTimer mFlashlightTurnedOnTimer;
StopwatchTimer mCameraTurnedOnTimer;
StopwatchTimer mForegroundActivityTimer;
- StopwatchTimer mBluetoothScanTimer;
+ DualTimer mBluetoothScanTimer;
int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
StopwatchTimer[] mProcessStateTimer;
@@ -5357,6 +5514,10 @@
mBsi = bsi;
mUid = uid;
+ mOnBatteryBackgroundTimeBase = new TimeBase();
+ mOnBatteryBackgroundTimeBase.init(mBsi.mClocks.uptimeMillis() * 1000,
+ mBsi.mClocks.elapsedRealtime() * 1000);
+
mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
mCpuPower = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
@@ -5383,8 +5544,8 @@
mBsi.mWifiRunningTimers, mBsi.mOnBatteryTimeBase);
mFullWifiLockTimer = new StopwatchTimer(mBsi.mClocks, this, FULL_WIFI_LOCK,
mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
- mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_SCAN,
- mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase);
+ mWifiScanTimer = new DualTimer(mBsi.mClocks, this, WIFI_SCAN,
+ mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS];
mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_MULTICAST_ENABLED,
mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase);
@@ -5471,8 +5632,9 @@
if (!mWifiScanStarted) {
mWifiScanStarted = true;
if (mWifiScanTimer == null) {
- mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
- mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase);
+ mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+ mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase,
+ mOnBatteryBackgroundTimeBase);
}
mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
}
@@ -5679,10 +5841,11 @@
return mForegroundActivityTimer;
}
- public StopwatchTimer createBluetoothScanTimerLocked() {
+ public DualTimer createBluetoothScanTimerLocked() {
if (mBluetoothScanTimer == null) {
- mBluetoothScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
- mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase);
+ mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+ mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+ mOnBatteryBackgroundTimeBase);
}
return mBluetoothScanTimer;
}
@@ -5755,7 +5918,7 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ return mWifiScanTimer.getMainTimer().getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
@@ -5763,7 +5926,33 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getCountLocked(which);
+ return mWifiScanTimer.getMainTimer().getCountLocked(which);
+ }
+
+ @Override
+ public int getWifiScanBackgroundCount(int which) {
+ if (mWifiScanTimer == null) {
+ return 0;
+ }
+ return mWifiScanTimer.getSubTimer().getCountLocked(which);
+ }
+
+ @Override
+ public long getWifiScanActualTime(final long elapsedRealtimeUs) {
+ if (mWifiScanTimer == null) {
+ return 0;
+ }
+ final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+ return mWifiScanTimer.getMainTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
+ }
+
+ @Override
+ public long getWifiScanBackgroundTime(final long elapsedRealtimeUs) {
+ if (mWifiScanTimer == null) {
+ return 0;
+ }
+ final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+ return mWifiScanTimer.getSubTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
}
@Override
@@ -5819,7 +6008,18 @@
@Override
public Timer getBluetoothScanTimer() {
- return mBluetoothScanTimer;
+ if (mBluetoothScanTimer == null) {
+ return null;
+ }
+ return mBluetoothScanTimer.getMainTimer();
+ }
+
+ @Override
+ public Timer getBluetoothScanBackgroundTimer() {
+ if (mBluetoothScanTimer == null) {
+ return null;
+ }
+ return mBluetoothScanTimer.getSubTimer();
}
void makeProcessState(int i, Parcel in) {
@@ -6216,6 +6416,9 @@
mLastStepUserTime = mLastStepSystemTime = 0;
mCurStepUserTime = mCurStepSystemTime = 0;
+ mOnBatteryBackgroundTimeBase.reset(mBsi.mClocks.elapsedRealtime() * 1000,
+ mBsi.mClocks.uptimeMillis() * 1000);
+
if (!active) {
if (mWifiRunningTimer != null) {
mWifiRunningTimer.detach();
@@ -6307,7 +6510,9 @@
return !active;
}
- void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ void writeToParcelLocked(Parcel out, long uptimeUs, long elapsedRealtimeUs) {
+ mOnBatteryBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
+
final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
int NW = wakeStats.size();
out.writeInt(NW);
@@ -6525,6 +6730,8 @@
}
void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
+ mOnBatteryBackgroundTimeBase.readFromParcel(in);
+
int numWakelocks = in.readInt();
mWakelockStats.clear();
for (int j = 0; j < numWakelocks; j++) {
@@ -6559,7 +6766,8 @@
for (int k = 0; k < numSensors; k++) {
int sensorNumber = in.readInt();
Uid.Sensor sensor = new Sensor(mBsi, this, sensorNumber);
- sensor.readFromParcelLocked(mBsi.mOnBatteryTimeBase, in);
+ sensor.readFromParcelLocked(mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+ in);
mSensorStats.put(sensorNumber, sensor);
}
@@ -6597,8 +6805,9 @@
}
mWifiScanStarted = false;
if (in.readInt() != 0) {
- mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
- mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, in);
+ mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+ mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+ in);
} else {
mWifiScanTimer = null;
}
@@ -6648,8 +6857,9 @@
mForegroundActivityTimer = null;
}
if (in.readInt() != 0) {
- mBluetoothScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
- mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase, in);
+ mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+ mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+ mOnBatteryBackgroundTimeBase, in);
} else {
mBluetoothScanTimer = null;
}
@@ -6946,14 +7156,12 @@
protected BatteryStatsImpl mBsi;
/**
- * BatteryStatsImpl that we are associated with.
+ * Uid that we are associated with.
*/
protected Uid mUid;
final int mHandle;
- StopwatchTimer mTimer;
-
- Counter mBgCounter;
+ DualTimer mTimer;
public Sensor(BatteryStatsImpl bsi, Uid uid, int handle) {
mBsi = bsi;
@@ -6961,7 +7169,8 @@
mHandle = handle;
}
- private StopwatchTimer readTimerFromParcel(TimeBase timeBase, Parcel in) {
+ private DualTimer readTimersFromParcel(
+ TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
if (in.readInt() == 0) {
return null;
}
@@ -6971,23 +7180,10 @@
pool = new ArrayList<StopwatchTimer>();
mBsi.mSensorTimers.put(mHandle, pool);
}
- return new StopwatchTimer(mBsi.mClocks, mUid, 0, pool, timeBase, in);
- }
-
- private Counter readCounterFromParcel(TimeBase timeBase, Parcel in) {
- if (in.readInt() == 0) {
- return null;
- }
- return new Counter(timeBase, in);
+ return new DualTimer(mBsi.mClocks, mUid, 0, pool, timeBase, bgTimeBase, in);
}
boolean reset() {
- if (mBgCounter != null) {
- mBgCounter.reset(true /*detachIfReset*/);
- // If we detach, we must null the mBgCounter reference so that it
- // can be recreated and attached.
- mBgCounter = null;
- }
if (mTimer.reset(true)) {
mTimer = null;
return true;
@@ -6995,24 +7191,28 @@
return false;
}
- void readFromParcelLocked(TimeBase timeBase, Parcel in) {
- mTimer = readTimerFromParcel(timeBase, in);
- mBgCounter = readCounterFromParcel(timeBase, in);
+ void readFromParcelLocked(TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
+ mTimer = readTimersFromParcel(timeBase, bgTimeBase, in);
}
void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
- Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
- Counter.writeCounterToParcel(out, mBgCounter);
+ DualTimer.writeDualTimerToParcel(out, mTimer, elapsedRealtimeUs);
}
@Override
public Timer getSensorTime() {
- return mTimer;
+ if (mTimer == null) {
+ return null;
+ }
+ return mTimer.getMainTimer();
}
@Override
- public Counter getSensorBgCount() {
- return mBgCounter;
+ public Timer getSensorBackgroundTime() {
+ if (mTimer == null) {
+ return null;
+ }
+ return mTimer.getSubTimer();
}
@Override
@@ -7749,18 +7949,29 @@
if (mProcessState == uidRunningState) return;
- final long elapsedRealtime = mBsi.mClocks.elapsedRealtime();
+ final long elapsedRealtimeMs = mBsi.mClocks.elapsedRealtime();
+ final long uptimeMs = mBsi.mClocks.uptimeMillis();
if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
- mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtime);
+ mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
}
mProcessState = uidRunningState;
if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
if (mProcessStateTimer[uidRunningState] == null) {
makeProcessState(uidRunningState, null);
}
- mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtime);
+ mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
}
+
+ updateBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
+ }
+
+ public boolean updateBgTimeBase(long uptimeUs, long realtimeUs) {
+ // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+ // also considered to be 'background' for our purposes, because it's not foreground.
+ boolean isBgAndUnplugged = mBsi.mOnBatteryTimeBase.isRunning()
+ && mProcessState >= PROCESS_STATE_BACKGROUND;
+ return mOnBatteryBackgroundTimeBase.setRunning(isBgAndUnplugged, uptimeUs, realtimeUs);
}
public SparseArray<? extends Pid> getPidStats() {
@@ -7834,7 +8045,7 @@
}
}
- public StopwatchTimer getSensorTimerLocked(int sensor, boolean create) {
+ public DualTimer getSensorTimerLocked(int sensor, boolean create) {
Sensor se = mSensorStats.get(sensor);
if (se == null) {
if (!create) {
@@ -7843,7 +8054,7 @@
se = new Sensor(mBsi, this, sensor);
mSensorStats.put(sensor, se);
}
- StopwatchTimer t = se.mTimer;
+ DualTimer t = se.mTimer;
if (t != null) {
return t;
}
@@ -7852,28 +8063,12 @@
timers = new ArrayList<StopwatchTimer>();
mBsi.mSensorTimers.put(sensor, timers);
}
- t = new StopwatchTimer(mBsi.mClocks, this, BatteryStats.SENSOR, timers,
- mBsi.mOnBatteryTimeBase);
+ t = new DualTimer(mBsi.mClocks, this, BatteryStats.SENSOR, timers,
+ mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
se.mTimer = t;
return t;
}
- public Counter getSensorBgCounterLocked(int sensor, boolean create) {
- Sensor se = mSensorStats.get(sensor);
- if (se == null) {
- if (!create) {
- return null;
- }
- se = new Sensor(mBsi, this, sensor);
- mSensorStats.put(sensor, se);
- }
- Counter c = se.mBgCounter;
- if (c != null) return c;
- c = new Counter(mBsi.mOnBatteryTimeBase);
- se.mBgCounter = c;
- return c;
- }
-
public void noteStartSyncLocked(String name, long elapsedRealtimeMs) {
StopwatchTimer t = mSyncStats.startObject(name);
if (t != null) {
@@ -7946,39 +8141,24 @@
}
public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
- StopwatchTimer t = getSensorTimerLocked(sensor, /* create= */ true);
+ DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
t.startRunningLocked(elapsedRealtimeMs);
-
- Counter c = getSensorBgCounterLocked(sensor, /* create= */ true);
- if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
- c.stepAtomic();
- }
}
public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
// Don't create a timer if one doesn't already exist
- StopwatchTimer t = getSensorTimerLocked(sensor, false);
+ DualTimer t = getSensorTimerLocked(sensor, false);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
}
}
public void noteStartGps(long elapsedRealtimeMs) {
- StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, /* create= */ true);
- t.startRunningLocked(elapsedRealtimeMs);
-
- Counter c = getSensorBgCounterLocked(Sensor.GPS, /* create= */ true);
- if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
- c.stepAtomic();
- }
+ noteStartSensor(Sensor.GPS, elapsedRealtimeMs);
}
public void noteStopGps(long elapsedRealtimeMs) {
- // Don't create a timer if one doesn't already exist
- StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
- if (t != null) {
- t.stopRunningLocked(elapsedRealtimeMs);
- }
+ noteStopSensor(Sensor.GPS, elapsedRealtimeMs);
}
public BatteryStatsImpl getBatteryStats() {
@@ -8969,7 +9149,7 @@
final Uid uid = mUidStats.valueAt(i);
// Sum the total scan power for all apps.
- totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
+ totalScanTimeMs += uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
// Sum the total time holding wifi lock for all apps.
@@ -8990,7 +9170,7 @@
for (int i = 0; i < uidStatsSize; i++) {
final Uid uid = mUidStats.valueAt(i);
- long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -9244,7 +9424,7 @@
mHasBluetoothReporting = true;
- final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+ final long elapsedRealtimeMs = mClocks.elapsedRealtime();
final long rxTimeMs = info.getControllerRxTimeMillis();
final long txTimeMs = info.getControllerTxTimeMillis();
@@ -9264,7 +9444,7 @@
continue;
}
- totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+ totalScanTimeMs += u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
}
@@ -9285,7 +9465,7 @@
continue;
}
- long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -10791,6 +10971,8 @@
Uid u = new Uid(this, uid);
mUidStats.put(uid, u);
+ u.mOnBatteryBackgroundTimeBase.readSummaryFromParcel(in);
+
u.mWifiRunning = false;
if (in.readInt() != 0) {
u.mWifiRunningTimer.readSummaryFromParcelLocked(in);
@@ -10948,8 +11130,7 @@
for (int is = 0; is < NP; is++) {
int seNumber = in.readInt();
if (in.readInt() != 0) {
- u.getSensorTimerLocked(seNumber, true)
- .readSummaryFromParcelLocked(in);
+ u.getSensorTimerLocked(seNumber, true).readSummaryFromParcelLocked(in);
}
}
@@ -11155,6 +11336,8 @@
out.writeInt(mUidStats.keyAt(iu));
Uid u = mUidStats.valueAt(iu);
+ u.mOnBatteryBackgroundTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+
if (u.mWifiRunningTimer != null) {
out.writeInt(1);
u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -11738,7 +11921,7 @@
out.writeInt(mUidStats.keyAt(i));
Uid uid = mUidStats.valueAt(i);
- uid.writeToParcelLocked(out, uSecRealtime);
+ uid.writeToParcelLocked(out, uSecUptime, uSecRealtime);
}
} else {
out.writeInt(0);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 373bda9..90aee9e 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -178,11 +178,7 @@
// Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
// use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
char subdir[PROP_VALUE_MAX];
- int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PERSIST_PROPERTY,
- subdir);
- if (len == 0) {
- len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
- }
+ int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
if (len > 0) {
String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
if (stat(overlayPath.string(), &st) == 0) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8abd022..8721f34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3344,6 +3344,19 @@
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
+ <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity"
+ android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:process=":ui">
+ <intent-filter>
+ <action android:name="android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<activity android:name="com.android.internal.app.IntentForwarderActivity"
android:finishOnCloseSystemDialogs="true"
android:theme="@style/Theme.NoDisplay"
diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml
new file mode 100644
index 0000000..0ef785f
--- /dev/null
+++ b/core/res/res/layout/accessibility_button_chooser.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2017, 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.
+*/
+-->
+<com.android.internal.widget.ResolverDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:maxWidth="@dimen/resolver_max_width"
+ android:maxCollapsedHeight="256dp"
+ android:maxCollapsedHeightSmall="56dp"
+ android:id="@id/contentPanel">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="?attr/colorBackground"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="56dp"
+ android:textAppearance="?attr/textAppearanceMedium"
+ android:text="@string/accessibility_button_prompt_text"
+ android:gravity="start|center_vertical"
+ android:layout_alignParentStart="true"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"/>
+
+ <GridView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/accessibility_button_chooser_grid"
+ android:columnWidth="90dp"
+ android:numColumns="auto_fit"
+ android:verticalSpacing="10dp"
+ android:horizontalSpacing="10dp"
+ android:stretchMode="columnWidth"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:gravity="center"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/accessibility_button_prompt"
+ android:layout_alwaysShow="true"
+ android:textAppearance="?attr/textAppearanceMedium"
+ android:text="@string/accessibility_button_instructional_text"
+ android:gravity="start|center_vertical"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:visibility="gone"/>
+ </LinearLayout>
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
new file mode 100644
index 0000000..76a9308
--- /dev/null
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="80dp"
+ android:gravity="center"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:background="?attr/selectableItemBackgroundBorderless">
+
+ <ImageView android:id="@+id/accessibility_button_target_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginLeft="3dp"
+ android:layout_marginRight="3dp"
+ android:layout_marginBottom="3dp"
+ android:scaleType="fitCenter"/>
+
+ <TextView android:id="@+id/accessibility_button_target_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginLeft="4dp"
+ android:layout_marginRight="4dp"
+ android:textAppearance="?attr/textAppearanceSmall"
+ android:textColor="?attr/textColorPrimary"
+ android:textSize="12sp"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="top|center_horizontal"
+ android:minLines="2"
+ android:maxLines="2"
+ android:ellipsize="marquee"/>
+</LinearLayout>
+
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b9409f2..ed5a42b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1191,6 +1191,20 @@
<p>The default value is <code>false</code>. -->
<attr name="supportsPictureInPicture" format="boolean" />
+ <!-- This value indicates the maximum aspect ratio the activity supports. If the app runs on a
+ device with a wider aspect ratio, the system automatically letterboxes the app, leaving
+ portions of the screen unused so the app can run at its specified maximum aspect ratio.
+ <p>
+ Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+ form. For example, if the maximum aspect ratio is 7:3, set value to 2.33.
+ <p>
+ Value needs to be greater or equal to 1.0, otherwise it is ignored.
+ <p>
+ NOTE: This attribute is ignored if the activity has
+ {@link android.R.attr#resizeableActivity} set to true, since that means your activity
+ supports any size. -->
+ <attr name="maxAspectRatio" format="float" />
+
<!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
While in lockTask mode the system will not launch non-permitted tasks until
lockTask mode is disabled.
@@ -1408,6 +1422,7 @@
<attr name="defaultToDeviceProtectedStorage" format="boolean" />
<attr name="directBootAware" />
<attr name="resizeableActivity" />
+ <attr name="maxAspectRatio" />
<attr name="networkSecurityConfig" />
<!-- Declare the category of this app. Categories are used to cluster multiple apps
together into meaningful groups, such as when summarizing battery, network, or
@@ -2066,6 +2081,7 @@
<attr name="resumeWhilePausing" />
<attr name="resizeableActivity" />
<attr name="supportsPictureInPicture" />
+ <attr name="maxAspectRatio" />
<attr name="lockTaskMode" />
<attr name="showForAllUsers" />
<attr name="directBootAware" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d6b5527..876d44d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2812,6 +2812,7 @@
<public name="fontProviderCerts" />
<public name="iconTint" />
<public name="iconTintMode" />
+ <public name="maxAspectRatio"/>
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9d2c6a7..868e256 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3929,22 +3929,24 @@
<!-- Dialog title for dialog shown when the accessibility shortcut is activated, and we want
to confirm that the user understands what's going to happen-->
- <string name="accessibility_shortcut_warning_dialog_title">Accessibility Shortcut is ON</string>
+ <string name="accessibility_shortcut_warning_dialog_title">Use Accessibility Shortcut?</string>
<!-- Message shown in dialog when user is in the process of enabling the accessibility
service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_toogle_warning">
- Turn <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> on or off by holding down
- both volume buttons for 3 seconds.\n\nYou can change the service in
- Settings > Accessibility.
+ When the shortcut is on, pressing both volume buttons for 3 seconds will start an
+ accessibility feature.\n\n
+ Current accessibility feature:\n
+ <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>\n\n
+ You can change the feature in Settings > Accessibility.
</string>
<!-- Text in button that turns off the accessibility shortcut -->
- <string name="disable_accessibility_shortcut">Turn Off Shortcut</string>
+ <string name="disable_accessibility_shortcut">Turn off Shortcut</string>
<!-- Text in button that closes the warning dialog about the accessibility shortcut, leaving the
shortcut enabled.-->
- <string name="leave_accessibility_shortcut_on">Leave on</string>
+ <string name="leave_accessibility_shortcut_on">Use Shortcut</string>
<!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility
service.-->
@@ -3956,6 +3958,15 @@
<string name="accessibility_shortcut_disabling_service">Accessibility Shortcut turned
<xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> off</string>
+ <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
+ <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the Accessibility button:</string>
+
+ <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
+ <string name="accessibility_button_instructional_text">To change features, touch & hold the Accessibility button.</string>
+
+ <!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. -->
+ <string name="accessibility_magnification_chooser_text">Magnification</string>
+
<!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
<string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Message shown when switching to a user [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f4d490a..b23c96c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2841,6 +2841,15 @@
<java-symbol type="string" name="leave_accessibility_shortcut_on" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
+ <!-- Accessibility Button -->
+ <java-symbol type="layout" name="accessibility_button_chooser" />
+ <java-symbol type="layout" name="accessibility_button_chooser_item" />
+ <java-symbol type="id" name="accessibility_button_chooser_grid" />
+ <java-symbol type="id" name="accessibility_button_prompt" />
+ <java-symbol type="id" name="accessibility_button_target_icon" />
+ <java-symbol type="id" name="accessibility_button_target_label" />
+ <java-symbol type="string" name="accessibility_magnification_chooser_text" />
+
<!-- com.android.internal.widget.RecyclerView -->
<java-symbol type="id" name="item_touch_helper_previous_elevation"/>
<java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 7deff06..9911d9d 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -790,6 +790,8 @@
<item name="colorAccent">@color/accent_device_default_light</item>
</style>
+ <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar"/>
+
<!-- Theme used for the intent picker activity. -->
<style name="Theme.DeviceDefault.Resolver" parent="Theme.Material.Light">
<item name="windowIsTranslucent">true</item>
diff --git a/core/tests/coretests/README b/core/tests/coretests/README
index 4a69843..aced441 100644
--- a/core/tests/coretests/README
+++ b/core/tests/coretests/README
@@ -45,6 +45,10 @@
-e debug true
+To uninstall the package:
+
+ adb shell pm uninstall -k com.android.frameworks.coretests
+
For more arguments, see the guide to command=line testing:
https://developer.android.com/studio/test/command-line.html
diff --git a/core/tests/coretests/src/android/content/ContentTests.java b/core/tests/coretests/src/android/content/ContentTests.java
index a1299e3..567b79a 100644
--- a/core/tests/coretests/src/android/content/ContentTests.java
+++ b/core/tests/coretests/src/android/content/ContentTests.java
@@ -23,6 +23,7 @@
TestSuite suite = new TestSuite(ContentTests.class.getName());
suite.addTestSuite(AssetTest.class);
+ suite.addTestSuite(ContentValuesTest.class);
return suite;
}
}
diff --git a/core/tests/coretests/src/android/content/ContentValuesTest.java b/core/tests/coretests/src/android/content/ContentValuesTest.java
new file mode 100644
index 0000000..7b39939
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContentValuesTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+
+/*
+ runtest -c android.content.ContentValuesTest frameworks-core
+
+ or
+
+ make -j256 FrameworksCoreTests && \
+ adb shell pm uninstall -k com.android.frameworks.coretests && \
+ adb install out/target/product/bullhead/testcases/FrameworksCoreTests/FrameworksCoreTests.apk && \
+ adb shell am instrument -w -e package android.content \
+ com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+*/
+public class ContentValuesTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testIsEmpty() throws Exception {
+ ContentValues cv = new ContentValues();
+ assertTrue(cv.isEmpty());
+ assertEquals(0, cv.size());
+
+ cv.put("key", "value");
+ assertFalse(cv.isEmpty());
+ assertEquals(1, cv.size());
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3e33dd8..782a50f 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -328,6 +328,7 @@
Settings.Global.USE_GOOGLE_MAIL,
Settings.Global.VT_IMS_ENABLED,
Settings.Global.WAIT_FOR_DEBUGGER,
+ Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
Settings.Global.WARNING_TEMPERATURE,
Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
new file mode 100644
index 0000000..6b52b98
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+
+import android.app.ActivityManager;
+import android.os.BatteryStats;
+import android.os.WorkSource;
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Test BatteryStatsImpl onBatteryBackgroundTimeBase TimeBase.
+ */
+public class BatteryStatsBackgroundStatsTest extends TestCase {
+
+ private static final int UID = 10500;
+
+ /** Test that BatteryStatsImpl.Uid.mOnBatteryBackgroundTimeBase works correctly. */
+ @SmallTest
+ public void testBgTimeBase() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long cur = 0; // realtime in us
+
+ BatteryStatsImpl.TimeBase bgtb = bi.getOnBatteryBackgroundTimeBase(UID);
+
+ // Off-battery, non-existent
+ clocks.realtime = clocks.uptime = 10;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+ assertFalse(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, foreground
+ clocks.realtime = clocks.uptime = 100;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ assertFalse(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, background
+ clocks.realtime = clocks.uptime = 201;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ assertFalse(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // On-battery, background
+ clocks.realtime = clocks.uptime = 303;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(true, false, cur, cur); // on battery
+ // still in ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ assertTrue(bgtb.isRunning());
+ assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // On-battery, background - but change screen state
+ clocks.realtime = clocks.uptime = 409;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(true, true, cur, cur); // on battery (again)
+ assertTrue(bgtb.isRunning());
+ assertEquals(106_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // On-battery, background - but a different background state
+ clocks.realtime = clocks.uptime = 417;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER); // background too
+ assertTrue(bgtb.isRunning());
+ assertEquals(114_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, foreground
+ clocks.realtime = clocks.uptime = 530;
+ cur = clocks.realtime * 1000;
+ bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ assertFalse(bgtb.isRunning());
+ assertEquals(227_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+ // Off-battery, non-existent
+ clocks.realtime = clocks.uptime = 690;
+ cur = clocks.realtime * 1000;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_NONEXISTENT);
+ assertFalse(bgtb.isRunning());
+ assertEquals(227_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testWifiScan() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // On battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+ // App in foreground
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteWifiScanStartedLocked(UID);
+
+ // Move to background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // Off battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteWifiScanStoppedLocked(UID);
+
+ // Test
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ long time = bi.getUidStats().get(UID).getWifiScanTime(curr, STATS_SINCE_CHARGED);
+ int count = bi.getUidStats().get(UID).getWifiScanCount(STATS_SINCE_CHARGED);
+ int bgCount = bi.getUidStats().get(UID).getWifiScanBackgroundCount(STATS_SINCE_CHARGED);
+ long actualTime = bi.getUidStats().get(UID).getWifiScanActualTime(curr);
+ long bgTime = bi.getUidStats().get(UID).getWifiScanBackgroundTime(curr);
+ assertEquals((305 - 202) * 1000, time);
+ assertEquals(1, count);
+ assertEquals(1, bgCount);
+ assertEquals((305 - 202) * 1000, actualTime);
+ assertEquals((305 - 254) * 1000, bgTime);
+ }
+
+ @SmallTest
+ public void testAppBluetoothScan() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ WorkSource ws = new WorkSource(UID); // needed for bluetooth
+ long curr = 0; // realtime in us
+
+ // On battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+
+ // App in foreground
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteBluetoothScanStartedFromSourceLocked(ws);
+
+ // Move to background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // Off battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteBluetoothScanStoppedFromSourceLocked(ws);
+
+ // Test
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ BatteryStats.Timer timer = bi.getUidStats().get(UID).getBluetoothScanTimer();
+ BatteryStats.Timer bgTimer = bi.getUidStats().get(UID).getBluetoothScanBackgroundTimer();
+
+ long time = timer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED);
+ int count = timer.getCountLocked(STATS_SINCE_CHARGED);
+ int bgCount = bgTimer.getCountLocked(STATS_SINCE_CHARGED);
+ long actualTime = timer.getTotalDurationMsLocked(clocks.realtime) * 1000;
+ long bgTime = bgTimer.getTotalDurationMsLocked(clocks.realtime) * 1000;
+ assertEquals((305 - 202) * 1000, time);
+ assertEquals(1, count);
+ assertEquals(1, bgCount);
+ assertEquals((305 - 202) * 1000, actualTime);
+ assertEquals((305 - 254) * 1000, bgTime);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
index f1aeecc..a1b05cd 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
@@ -44,18 +44,21 @@
assertFalse(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(300));
assertEquals(0, timer.getMaxDurationMsLocked(301));
+ assertEquals(0, timer.getTotalDurationMsLocked(301));
- // Start timer: current and max advance
+ // Start timer: current, total, and max advance
timer.startRunningLocked(700);
assertTrue(timer.isRunningLocked());
assertEquals(800, timer.getCurrentDurationMsLocked(1500));
assertEquals(801, timer.getMaxDurationMsLocked(1501));
+ assertEquals(802, timer.getTotalDurationMsLocked(1502));
- // Stop timer: current resets to 0, max remains
+ // Stop timer: current resets to 0; total and max remain
timer.stopRunningLocked(3100);
assertFalse(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(6300));
assertEquals(2400, timer.getMaxDurationMsLocked(6301));
+ assertEquals(2400, timer.getTotalDurationMsLocked(6302));
// Start time again, but check with a short time, and make sure max doesn't
// increment.
@@ -63,31 +66,36 @@
assertTrue(timer.isRunningLocked());
assertEquals(100, timer.getCurrentDurationMsLocked(12800));
assertEquals(2400, timer.getMaxDurationMsLocked(12801));
+ assertEquals(2502, timer.getTotalDurationMsLocked(12802));
// And stop it again, but with a short time, and make sure it doesn't increment.
timer.stopRunningLocked(12900);
assertFalse(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(13000));
assertEquals(2400, timer.getMaxDurationMsLocked(13001));
+ assertEquals(2600, timer.getTotalDurationMsLocked(13002));
// Now start and check that the time doesn't increase if the two times are the same.
timer.startRunningLocked(27000);
assertTrue(timer.isRunningLocked());
assertEquals(0, timer.getCurrentDurationMsLocked(27000));
assertEquals(2400, timer.getMaxDurationMsLocked(27000));
+ assertEquals(2600, timer.getTotalDurationMsLocked(27000));
// Stop the TimeBase. The values should be frozen.
timeBase.setRunning(false, /* uptimeUs */ 10, /* realtimeUs */ 55000*1000);
assertTrue(timer.isRunningLocked());
assertEquals(28000, timer.getCurrentDurationMsLocked(110100));
assertEquals(28000, timer.getMaxDurationMsLocked(110101));
+ assertEquals(30600, timer.getTotalDurationMsLocked(110102));
// Start the TimeBase. The values should be the old value plus the delta
- // between when the timer restarted and the current time
+ // between when the timer restarted and the current time.
timeBase.setRunning(true, /* uptimeUs */ 10, /* realtimeUs */ 220100*1000);
assertTrue(timer.isRunningLocked());
assertEquals(28200, timer.getCurrentDurationMsLocked(220300));
assertEquals(28201, timer.getMaxDurationMsLocked(220301));
+ assertEquals(30802, timer.getTotalDurationMsLocked(220302));
}
@SmallTest
@@ -114,6 +122,7 @@
// Check that it did start running
assertEquals(400, timer.getMaxDurationMsLocked(700));
assertEquals(401, timer.getCurrentDurationMsLocked(701));
+ assertEquals(402, timer.getTotalDurationMsLocked(702));
// Write summary
final Parcel summaryParcel = Parcel.obtain();
@@ -128,8 +137,9 @@
// The new one shouldn't be running, and therefore 0 for current time
assertFalse(summary.isRunningLocked());
assertEquals(0, summary.getCurrentDurationMsLocked(6300));
- // The new one should have the max duration that we had when we wrote it
+ // The new one should have the max and total durations that we had when we wrote it
assertEquals(1200, summary.getMaxDurationMsLocked(6301));
+ assertEquals(1200, summary.getTotalDurationMsLocked(6302));
// Write full
final Parcel fullParcel = Parcel.obtain();
@@ -142,7 +152,9 @@
// The new one shouldn't be running, and therefore 0 for current time
assertFalse(full.isRunningLocked());
assertEquals(0, full.getCurrentDurationMsLocked(6300));
- // The new one should have the max duration that we had when we wrote it
+ // The new one should have the max and total durations that we had when we wrote it
assertEquals(1200, full.getMaxDurationMsLocked(6301));
+ assertEquals(1200, full.getTotalDurationMsLocked(6302));
+
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
index b4afdda..251ceb0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
@@ -146,6 +146,8 @@
BatteryStatsImpl.SamplingTimer timer = new BatteryStatsImpl.SamplingTimer(clocks, timeBase);
// Start running on battery.
+ // (Note that the wrong units are used in this class. setRunning is actually supposed to
+ // take us, not the ms that clocks uses.)
timeBase.setRunning(true, clocks.uptimeMillis(), clocks.elapsedRealtime());
// The first update on battery consumes the values as a way of starting cleanly.
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index 4ec78ff..a41a023 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -17,9 +17,7 @@
import android.app.ActivityManager;
import android.os.BatteryStats;
-import android.os.Debug;
import android.support.test.filters.SmallTest;
-import android.util.Log;
import junit.framework.TestCase;
@@ -31,6 +29,9 @@
private static final int UID = 10500;
private static final int SENSOR_ID = -10000;
+ // TODO: fix the bug in StopwatchTimer to prevent this bug from manifesting here.
+ boolean revealCntBug = false;
+
@SmallTest
public void testSensorStartStop() throws Exception {
final MockClocks clocks = new MockClocks();
@@ -38,7 +39,7 @@
bi.mForceOnBattery = true;
clocks.realtime = 100;
clocks.uptime = 100;
- bi.getOnBatteryTimeBase().setRunning(true, 100, 100);
+ bi.getOnBatteryTimeBase().setRunning(true, 100_000, 100_000);
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
bi.noteStartSensorLocked(UID, SENSOR_ID);
@@ -56,58 +57,345 @@
BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
.get(SENSOR_ID).getSensorTime();
- BatteryStats.Counter sensorBgCounter = bi.getUidStats().get(UID).getSensorStats()
- .get(SENSOR_ID).getSensorBgCount();
+ BatteryStats.Timer sensorBgTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorBackgroundTime();
assertEquals(2, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
- assertEquals(300000,
- sensorTimer.getTotalTimeLocked(clocks.realtime, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(300_000, sensorTimer.getTotalTimeLocked(
+ clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED));
- assertEquals(1, sensorBgCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(1, sensorBgTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(200_000, sensorBgTimer.getTotalTimeLocked(
+ clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED));
}
@SmallTest
- public void testNestedSensorReset() throws Exception {
+ public void testCountingWhileOffBattery() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // Plugged-in (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(false, false, curr, curr);
+
+
+ // Start sensor (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals(0,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Stop sensor (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals(0,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testCountingWhileOnBattery() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // Unplugged (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
+
+ // Start sensor (battery=on, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals((215-200)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+
+ // Stop sensor (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals((550-200)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testBatteryStatusOnToOff() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // On battery (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start sensor (battery=on, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Off battery (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr);
+
+ // Stop sensor while off battery (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Start sensor while off battery (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 519);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test while still running (but off battery)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ if(revealCntBug) {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(2, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+ assertEquals((305-202)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+
+ // Now stop running (still off battery) (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 693);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals((305-202)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ @SmallTest
+ public void testBatteryStatusOffToOn() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ long curr = 0; // realtime in us
+
+ // Plugged-in (battery=off, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(false, false, curr, curr);
+
+ // Start sensor (battery=off, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+ BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ // Time was entirely off battery, so time=0.
+ assertEquals(0,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Acquired off battery, so count=0.
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Unplug (battery=on, sensor=on)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
+
+ //Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 410);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats().get(SENSOR_ID).getSensorTime();
+ // Part of the time it was on battery.
+ assertEquals((410-305)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Only ever acquired off battery, so count=0.
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Stop sensor (battery=on, sensor=off)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+ // Test situation
+ curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+ sensorTimer = bi.getUidStats().get(UID).getSensorStats().get(SENSOR_ID).getSensorTime();
+ // Part of the time it was on battery.
+ assertEquals((550-305)*1000,
+ sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Only ever acquired off battery, so count=0.
+ if(revealCntBug) {
+ assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+ }
+
+ @SmallTest
+ public void testPooledBackgroundUsage() throws Exception {
+ final int UID_2 = 20000; // second uid for testing pool usage
final MockClocks clocks = new MockClocks();
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
bi.mForceOnBattery = true;
- clocks.realtime = 100;
- clocks.uptime = 100;
- bi.getOnBatteryTimeBase().setRunning(true, 100, 100);
- bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER);
+ long curr = 0; // realtime in us
+ // Entire test is on-battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 1000);
+ bi.updateTimeBasesLocked(true, false, curr, curr);
- clocks.realtime += 100;
- clocks.uptime += 100;
+ // See below for a diagram of events.
+ // UID in foreground
+ curr = 1000 * (clocks.realtime = clocks.uptime = 2002);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // UID starts the sensor (foreground)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 3004);
bi.noteStartSensorLocked(UID, SENSOR_ID);
- clocks.realtime += 100;
- clocks.uptime += 100;
+ // UID_2 in background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 4008);
+ bi.noteUidProcessStateLocked(UID_2, ActivityManager.PROCESS_STATE_RECEIVER); // background
- // The sensor is started and the background counter has been created.
- final BatteryStats.Uid uid = bi.getUidStats().get(UID);
- assertNotNull(uid);
+ // UID_2 starts the sensor (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 5016);
+ bi.noteStartSensorLocked(UID_2, SENSOR_ID);
- BatteryStats.Uid.Sensor sensor = uid.getSensorStats().get(SENSOR_ID);
- assertNotNull(sensor);
- assertNotNull(sensor.getSensorTime());
- assertNotNull(sensor.getSensorBgCount());
+ // UID enters background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 6032);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
- // Reset the stats. Since the sensor is still running, we should still see the sensor
- // timer. Background counter should be gone though.
- bi.getUidStatsLocked(UID).reset();
+ // UID enters background again (from a different background state)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 7004);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
- sensor = uid.getSensorStats().get(SENSOR_ID);
- assertNotNull(sensor);
- assertNotNull(sensor.getSensorTime());
- assertNull(sensor.getSensorBgCount());
+ // UID_2 stops the sensor (background), then starts it again, then stops again
+ curr = 1000 * (clocks.realtime = clocks.uptime = 8064);
+ bi.noteStopSensorLocked(UID_2, SENSOR_ID);
+ curr = 1000 * (clocks.realtime = clocks.uptime = 9128);
+ bi.noteStartSensorLocked(UID_2, SENSOR_ID);
+ curr = 1000 * (clocks.realtime = clocks.uptime = 10256);
+ bi.noteStopSensorLocked(UID_2, SENSOR_ID);
+ // UID re-enters foreground
+ curr = 1000 * (clocks.realtime = clocks.uptime = 11512);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // UID starts the sensor a second time (foreground)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 12000);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+ // UID re-enters background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 13002);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // UID stops the sensor completely (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 14004);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+ curr = 1000 * (clocks.realtime = clocks.uptime = 14024);
bi.noteStopSensorLocked(UID, SENSOR_ID);
- // Now the sensor timer has stopped so this reset should also take out the sensor.
- bi.getUidStatsLocked(UID).reset();
+ // UID starts the sensor anew (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 15010);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
- sensor = uid.getSensorStats().get(SENSOR_ID);
- assertNull(sensor);
+ // UID stops the sensor (background)
+ curr = 1000 * (clocks.realtime = clocks.uptime = 16020);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+// Summary
+// UID
+// foreground: 2002---6032, 11512---13002
+// background: 6032---------------11512, 13002--------------------------
+// sensor running: 3004-----------------------------14024, 15010-16020
+//
+// UID2
+// foreground:
+// background: 4008-------------------------------------------------------
+// sensor running: 5016--8064, 9128-10256
+
+ BatteryStats.Timer timer1 = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ BatteryStats.Timer bgTimer1 = bi.getUidStats().get(UID).getSensorStats()
+ .get(SENSOR_ID).getSensorBackgroundTime();
+
+ BatteryStats.Timer timer2 = bi.getUidStats().get(UID_2).getSensorStats()
+ .get(SENSOR_ID).getSensorTime();
+ BatteryStats.Timer bgTimer2 = bi.getUidStats().get(UID_2).getSensorStats()
+ .get(SENSOR_ID).getSensorBackgroundTime();
+
+ // Expected values
+ long expActualTime1 = (14024 - 3004) + (16020 - 15010);
+ long expBgTime1 = (11512 - 6032) + (14024 - 13002) + (16020 - 15010);
+
+ long expActualTime2 = (8064 - 5016) + (10256 - 9128);
+ long expBgTime2 = (8064 - 5016) + (10256 - 9128);
+
+ long expBlamedTime1 = (5016 - 3004) + (8064 - 5016)/2 + (9128 - 8064) + (10256 - 9128)/2
+ + (14024 - 10256) + (16020 - 15010);
+ long expBlamedTime2 = (8064 - 5016)/2 + (10256 - 9128)/2;
+
+ // Test: UID - blamed time
+ assertEquals(expBlamedTime1 * 1000,
+ timer1.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID - actual time
+ assertEquals(expActualTime1 * 1000,
+ timer1.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+ // Test: UID - background time
+ // bg timer ignores pools, so both totalTime and totalDuration should give the same result
+ assertEquals(expBgTime1 * 1000,
+ bgTimer1.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(expBgTime1 * 1000,
+ bgTimer1.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+ // Test: UID - count
+ assertEquals(2, timer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID - background count
+ if(revealCntBug) {
+ assertEquals(1, bgTimer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ } else {
+ assertEquals(2, bgTimer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ }
+
+ // Test: UID_2 - blamed time
+ assertEquals(expBlamedTime2 * 1000,
+ timer2.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID_2 - actual time
+ assertEquals(expActualTime2 * 1000,
+ timer2.getTotalDurationMsLocked(clocks.realtime) * 1000);
+ // Test: UID_2 - background time
+ // bg timer ignores pools, so both totalTime and totalDuration should give the same result
+ assertEquals(expBgTime2 * 1000,
+ bgTimer2.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+ assertEquals(expBgTime2 * 1000,
+ bgTimer2.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+ // Test: UID_2 - count
+ assertEquals(2, timer2.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+ // Test: UID_2 - background count
+ assertEquals(2, bgTimer2.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index c7cd0ee..1113268 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -12,6 +12,7 @@
BatteryStatsTimerTest.class,
BatteryStatsUidTest.class,
BatteryStatsSensorTest.class,
+ BatteryStatsBackgroundStatsTest.class,
})
public class BatteryStatsTests {
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 1054106..65f898c 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -26,6 +26,7 @@
MockBatteryStatsImpl(Clocks clocks) {
super(clocks);
this.clocks = mClocks;
+ mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
}
MockBatteryStatsImpl() {
@@ -39,5 +40,9 @@
public boolean isOnBattery() {
return mForceOnBattery ? true : super.isOnBattery();
}
+
+ public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
+ return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
+ }
}
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index acacd76..5603508 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -74,7 +74,6 @@
const char* AssetManager::IDMAP_BIN = "/system/bin/idmap";
const char* AssetManager::OVERLAY_DIR = "/vendor/overlay";
const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme";
-const char* AssetManager::OVERLAY_THEME_DIR_PERSIST_PROPERTY = "persist.vendor.overlay.theme";
const char* AssetManager::TARGET_PACKAGE_NAME = "android";
const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk";
const char* AssetManager::IDMAP_DIR = "/data/resource-cache";
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index becd307..0441b9d 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -66,11 +66,6 @@
* OVERLAY_DIR.
*/
static const char* OVERLAY_THEME_DIR_PROPERTY;
- /**
- * If OVERLAY_THEME_DIR_PERSIST_PROPERTY, use it to override
- * OVERLAY_THEME_DIR_PROPERTY.
- */
- static const char* OVERLAY_THEME_DIR_PERSIST_PROPERTY;
static const char* TARGET_PACKAGE_NAME;
static const char* TARGET_APK_PATH;
static const char* IDMAP_DIR;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
index 8f7d6ac..9e50490 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
@@ -25,6 +25,9 @@
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
@@ -805,6 +808,29 @@
mFailedToCompleteWithNoError = true;
String testResult;
+ final MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ final MediaExtractor extractor = new MediaExtractor();
+ boolean hasSupportedVideo = false;
+
+ try {
+ extractor.setDataSource(filePath);
+
+ for (int index = 0; index < extractor.getTrackCount(); ++index) {
+ MediaFormat format = extractor.getTrackFormat(index);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ if (!mime.startsWith("video/")) {
+ continue;
+ }
+
+ if (list.findDecoderForFormat(format) != null) {
+ hasSupportedVideo = true;
+ break;
+ }
+ }
+ } finally {
+ extractor.release();
+ }
+
initializeMessageLooper();
synchronized (lock) {
try {
@@ -820,7 +846,12 @@
mMediaPlayer.setOnInfoListener(mInfoListener);
Log.v(TAG, "playMediaSamples: sample file name " + filePath);
mMediaPlayer.setDataSource(filePath);
- mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
+ if (hasSupportedVideo) {
+ mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
+ } else {
+ Log.i(TAG, "Set no display due to no (supported) video track.");
+ mMediaPlayer.setDisplay(null);
+ }
mMediaPlayer.prepare();
duration = mMediaPlayer.getDuration();
// start to play
diff --git a/packages/SettingsLib/res/xml/timezones.xml b/packages/SettingsLib/res/xml/timezones.xml
index 4426495..12d31cf 100644
--- a/packages/SettingsLib/res/xml/timezones.xml
+++ b/packages/SettingsLib/res/xml/timezones.xml
@@ -21,7 +21,7 @@
<timezone id="America/St_Johns"></timezone>
<timezone id="America/Recife"></timezone>
<timezone id="America/Sao_Paulo"></timezone>
- <timezone id="America/Buenos_Aires"></timezone>
+ <timezone id="America/Argentina/Buenos_Aires"></timezone>
<timezone id="America/Godthab"></timezone>
<timezone id="America/Montevideo"></timezone>
<timezone id="Atlantic/South_Georgia"></timezone>
@@ -58,11 +58,11 @@
<timezone id="Asia/Karachi"></timezone>
<timezone id="Asia/Oral"></timezone>
<timezone id="Asia/Yekaterinburg"></timezone>
- <timezone id="Asia/Calcutta"></timezone>
+ <timezone id="Asia/Kolkata"></timezone>
<timezone id="Asia/Colombo"></timezone>
- <timezone id="Asia/Katmandu"></timezone>
+ <timezone id="Asia/Kathmandu"></timezone>
<timezone id="Asia/Almaty"></timezone>
- <timezone id="Asia/Rangoon"></timezone>
+ <timezone id="Asia/Yangon"></timezone>
<timezone id="Asia/Krasnoyarsk"></timezone>
<timezone id="Asia/Bangkok"></timezone>
<timezone id="Asia/Jakarta"></timezone>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 9bb3c36..350b648 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -169,6 +169,19 @@
return context.getString(R.string.config_defaultAccessibilityService);
}
+ /**
+ * Check if the accessibility shortcut is enabled for a user
+ *
+ * @param context A valid context
+ * @param userId The user of interest
+ * @return {@code true} if the shortcut is enabled for the user. {@code false} otherwise.
+ * Note that the shortcut may be enabled, but no action associated with it.
+ */
+ public static boolean isShortcutEnabled(Context context, int userId) {
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, userId) == 1;
+ }
+
private static Set<ComponentName> getInstalledServices(Context context) {
final Set<ComponentName> installedServices = new HashSet<>();
installedServices.clear();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 55c886e..0280f26 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -95,7 +95,7 @@
private WifiTrackerNetworkCallback mNetworkCallback;
- private boolean mSavedNetworksExist;
+ private int mNumSavedNetworks;
private boolean mRegistered;
/** Updated using main handler. Clone of this collection is returned
@@ -363,11 +363,11 @@
}
/**
- * @return true when there are saved networks on the device, regardless
- * of whether the WifiTracker is tracking saved networks.
+ * Returns the number of saved networks on the device, regardless of whether the WifiTracker
+ * is tracking saved networks.
*/
- public boolean doSavedNetworksExist() {
- return mSavedNetworksExist;
+ public int getNumSavedNetworks() {
+ return mNumSavedNetworks;
}
public boolean isConnected() {
@@ -461,11 +461,12 @@
final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
if (configs != null) {
- mSavedNetworksExist = configs.size() != 0;
+ mNumSavedNetworks = 0;
for (WifiConfiguration config : configs) {
if (config.selfAdded && config.numAssociation == 0) {
continue;
}
+ mNumSavedNetworks++;
AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index e100884..46726f2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -395,6 +395,26 @@
}
@Test
+ public void testGetNumSavedNetworks() throws InterruptedException {
+ WifiConfiguration validConfig = new WifiConfiguration();
+ validConfig.SSID = SSID_1;
+ validConfig.BSSID = BSSID_1;
+
+ WifiConfiguration selfAddedNoAssociation = new WifiConfiguration();
+ selfAddedNoAssociation.selfAdded = true;
+ selfAddedNoAssociation.numAssociation = 0;
+ selfAddedNoAssociation.SSID = SSID_2;
+ selfAddedNoAssociation.BSSID = BSSID_2;
+
+ when(mockWifiManager.getConfiguredNetworks())
+ .thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation));
+
+ WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
+
+ assertEquals(1, tracker.getNumSavedNetworks());
+ }
+
+ @Test
public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index f5c3d40..deb494f 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -25,5 +25,5 @@
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical|start"
- android:paddingStart="@dimen/battery_level_padding_start"
+ android:paddingEnd="@dimen/battery_level_padding_start"
/>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 14f2c4a..8c1062b 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -181,6 +181,7 @@
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
updatePercentText();
addView(mBatteryPercentView,
+ 0,
new ViewGroup.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d058e78..79190cb 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -24,6 +24,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.NightDisplayController;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.assist.AssistManager;
@@ -257,6 +258,8 @@
mProviders.put(VolumeDialogController.class, () ->
new VolumeDialogControllerImpl(mContext));
+ mProviders.put(MetricsLogger.class, () -> new MetricsLogger());
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8580085..fbb075a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -95,6 +95,7 @@
*/
private final Class<?>[] SERVICES_PER_USER = new Class[] {
Dependency.class,
+ NotificationChannels.class,
Recents.class
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 1709718..9efe224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -14,6 +14,8 @@
package com.android.systemui.qs;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
@@ -197,7 +199,7 @@
mDetailContent.removeAllViews();
mDetailContent.addView(detailView);
mDetailViews.put(viewCacheIndex, detailView);
- MetricsLogger.visible(mContext, adapter.getMetricsCategory());
+ Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
announceForAccessibility(mContext.getString(
R.string.accessibility_quick_settings_detail,
adapter.getTitle()));
@@ -206,7 +208,7 @@
setVisibility(View.VISIBLE);
} else {
if (mDetailAdapter != null) {
- MetricsLogger.hidden(mContext, mDetailAdapter.getMetricsCategory());
+ Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
}
mClosingDetail = true;
mDetailAdapter = null;
@@ -238,8 +240,12 @@
protected void setupDetailFooter(DetailAdapter adapter) {
final Intent settingsIntent = adapter.getSettingsIntent();
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
- mDetailSettingsButton.setOnClickListener(v -> Dependency.get(ActivityStarter.class)
- .postStartActivityDismissingKeyguard(settingsIntent, 0));
+ mDetailSettingsButton.setOnClickListener(v -> {
+ Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
+ mDetailAdapter.getMetricsCategory());
+ Dependency.get(ActivityStarter.class)
+ .postStartActivityDismissingKeyguard(settingsIntent, 0);
+ });
}
protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 2202b58..a84138d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
+
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -358,6 +360,8 @@
startSettingsActivity();
}
} else if (v == mDateTimeGroup) {
+ Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
+ mNextAlarm != null);
if (mNextAlarm != null) {
PendingIntent showIntent = mNextAlarm.getShowIntent();
mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 29d547c..8596b57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -32,6 +32,8 @@
TileServices getTileServices();
void removeTile(String tileSpec);
+ int indexOf(String tileSpec);
+
interface Callback {
void onTilesChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8298cbb..2e6116d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -59,6 +59,7 @@
protected final View mBrightnessView;
private final H mHandler = new H();
private final View mPageIndicator;
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private int mPanelPaddingBottom;
private int mBrightnessPaddingTop;
@@ -259,7 +260,7 @@
if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
}
- MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
closeDetail();
} else {
@@ -475,7 +476,7 @@
int newVis = visible ? VISIBLE : INVISIBLE;
setVisibility(newVis);
if (mGridContentVisible != visible) {
- MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
}
mGridContentVisible = visible;
}
@@ -483,7 +484,7 @@
private void logTiles() {
for (int i = 0; i < mRecords.size(); i++) {
TileRecord tileRecord = mRecords.get(i);
- MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
+ mMetricsLogger.visible(tileRecord.tile.getMetricsCategory());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca115e..9330541 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -157,6 +157,10 @@
return mServices;
}
+ public int indexOf(String spec) {
+ return mTileSpecs.indexOf(spec);
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 6f35017..b5c1bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -22,6 +22,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -155,6 +156,11 @@
return mComponent;
}
+ @Override
+ protected LogMaker populate(LogMaker logMaker) {
+ return super.populate(logMaker).setComponentName(mComponent);
+ }
+
public Tile getQsTile() {
return mTile;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 948954c2..1aa51b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -14,12 +14,19 @@
package com.android.systemui.qs.tileimpl;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -29,7 +36,6 @@
import android.util.SparseArray;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
@@ -58,6 +64,7 @@
protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
private final ArraySet<Object> mListeners = new ArraySet<>();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
protected TState mState = newTileState();
@@ -76,7 +83,7 @@
/**
* Declare the category of this tile.
*
- * Categories are defined in {@link com.android.internal.logging.MetricsProto.MetricsEvent}
+ * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}
* by editing frameworks/base/proto/src/metrics_constants.proto.
*/
abstract public int getMetricsCategory();
@@ -152,17 +159,28 @@
}
public void click() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.CLICK);
}
public void secondaryClick() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
}
public void longClick() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.LONG_CLICK);
}
+ protected LogMaker populate(LogMaker logMaker) {
+ if (mState instanceof BooleanState) {
+ logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);
+ }
+ return logMaker.setSubtype(getMetricsCategory())
+ .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
+ }
+
public void showDetail(boolean show) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
}
@@ -224,7 +242,6 @@
}
protected void handleLongClick() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
getLongClickIntent(), 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index ed6e6ef..4e4de15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -83,7 +83,6 @@
protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
final boolean isEnabled = (Boolean)mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setBluetoothEnabled(!isEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a1d3d26..22b6a63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -108,13 +108,11 @@
protected void handleClick() {
if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- MetricsLogger.action(mContext, getMetricsCategory());
showDetail(true);
mHost.openPanels();
});
return;
}
- MetricsLogger.action(mContext, getMetricsCategory());
showDetail(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 4351b2c..04be7de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -98,7 +98,6 @@
@Override
protected void handleSecondaryClick() {
- MetricsLogger.action(mContext, getMetricsCategory());
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index e33b680..5b374b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -84,7 +84,6 @@
@Override
protected void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mSetting.setValue(mState.value ? 0 : 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7a25140..b796451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -87,7 +87,6 @@
private void toggleDataSaver() {
mState.value = !mDataSaverController.isDataSaverEnabled();
- MetricsLogger.action(mContext, getMetricsCategory(), mState.value);
mDataSaverController.setDataSaverEnabled(mState.value);
refreshState(mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f35de68..3c2e897 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -146,7 +146,6 @@
Toast.LENGTH_LONG).show();
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
showDetail(true);
int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
mController.setZen(zen, null, TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 7b0fd73..6d2aa90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -87,7 +87,6 @@
if (ActivityManager.isUserAMonkey()) {
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
boolean newState = !mState.value;
refreshState(newState);
mFlashlightController.setFlashlight(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6662937..5c3f65c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -106,7 +106,6 @@
if (!isEnabled && mAirplaneMode.getValue() != 0) {
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setHotspotEnabled(!isEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index c953363..00cfbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -92,7 +92,6 @@
@Override
protected void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage);
sendIntent("click", mOnClick, mOnClickUri);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index b5c02cb..b11b15a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -81,13 +81,11 @@
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
final boolean wasEnabled = mState.value;
mHost.openPanels();
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
});
return;
}
final boolean wasEnabled = mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 3299339..d147b69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -85,7 +85,6 @@
@Override
protected void handleClick() {
if (mAdapter == null) return;
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
if (!mAdapter.isEnabled()) {
mAdapter.enable();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 8b47216..8aa1e43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -54,7 +54,6 @@
@Override
protected void handleClick() {
final boolean activated = !mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), activated);
mController.setActivated(activated);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 130304f..fb937bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -79,7 +79,6 @@
@Override
protected void handleClick() {
if (mController == null) return;
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
final boolean newState = !mState.value;
mController.setRotationLocked(!newState);
refreshState(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index fde2e04..79b4c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -114,7 +114,6 @@
protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
mState.copyTo(mStateBeforeClick);
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mController.setWifiEnabled(!mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 5086091..6c89241 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -68,7 +68,6 @@
@Override
public void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mProfileController.setWorkModeEnabled(!mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index b5f56c3..4d99a46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -22,6 +22,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
@@ -33,7 +34,7 @@
private ArrayMap<Integer, Integer> mLegacyMap;
private LogMaker mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
.setType(MetricsEvent.TYPE_ACTION);
- private MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
public LockscreenGestureLogger() {
mLegacyMap = new ArrayMap<>(EventLogConstants.METRICS_GESTURE_TYPE_MAP.length);
@@ -58,9 +59,4 @@
}
return value;
}
-
- @VisibleForTesting
- void setMetricsLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = metricsLogger;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 5fb642f..1f03024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -557,9 +557,9 @@
}
private boolean onAccessibilityLongClick(View v) {
- // TODO(b/34720082): Target service selection via long click
- android.widget.Toast.makeText(getContext(), "Service selection coming soon...",
- android.widget.Toast.LENGTH_LONG).show();
+ Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ v.getContext().startActivity(intent);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b82b113..bfe55bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -484,7 +484,7 @@
private ScreenPinningRequest mScreenPinningRequest;
- MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
@@ -748,12 +748,6 @@
private NavigationBarFragment mNavigationBar;
private View mNavigationBarView;
- @VisibleForTesting
- void setMetricsLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = metricsLogger;
- mLockscreenGestureLogger.setMetricsLogger(metricsLogger);
- }
-
@Override
public void start() {
mNetworkController = Dependency.get(NetworkController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
new file mode 100644
index 0000000..c67cccc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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.qs;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.DetailAdapter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSDetailTest extends SysuiTestCase {
+
+ private MetricsLogger mMetricsLogger;
+ private QSDetail mQsDetail;
+ private QSPanel mQsPanel;
+ private QuickStatusBarHeader mQuickHeader;
+ private ActivityStarter mActivityStarter;
+ private DetailAdapter mMockDetailAdapter;
+ private TestableLooper mTestableLooper;
+
+ @Before
+ public void setup() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+ mTestableLooper.runWithLooper(() -> {
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+ mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
+ mQsPanel = mock(QSPanel.class);
+ mQuickHeader = mock(QuickStatusBarHeader.class);
+ mQsDetail.setQsPanel(mQsPanel, mQuickHeader);
+
+ mMockDetailAdapter = mock(DetailAdapter.class);
+ when(mMockDetailAdapter.createDetailView(any(), any(), any()))
+ .thenReturn(mock(View.class));
+ });
+ }
+
+ @Test
+ public void testShowDetail_Metrics() {
+ ViewUtils.attachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+
+ mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+ verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory()));
+ mQsDetail.handleShowingDetail(null, 0, 0, false);
+ verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory()));
+
+ ViewUtils.detachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void testMoreSettingsButton() {
+ ViewUtils.attachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+
+ mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+ mQsDetail.findViewById(android.R.id.button2).performClick();
+
+ int metricsCategory = mMockDetailAdapter.getMetricsCategory();
+ verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory));
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
+
+ ViewUtils.detachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index deb31da..d77ed3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -19,6 +19,7 @@
import android.os.Looper;
+import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.CarrierText;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -44,12 +45,15 @@
@RunWithLooper(setAsMainLooper = true)
public class QSFragmentTest extends SysuiBaseFragmentTest {
+ private MetricsLogger mMockMetricsLogger;
+
public QSFragmentTest() {
super(QSFragment.class);
}
@Before
public void addLeakCheckDependencies() {
+ mMockMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
new LayoutInflaterBuilder(mContext)
.replace("com.android.systemui.statusbar.policy.SplitClockView",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
new file mode 100644
index 0000000..4979684
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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.qs;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.customize.QSCustomizer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSPanelTest extends SysuiTestCase {
+
+ private MetricsLogger mMetricsLogger;
+ private QSPanel mQsPanel;
+ private QSTileHost mHost;
+ private QSCustomizer mCustomizer;
+
+ @Before
+ public void setup() throws Exception {
+ TestableLooper.get(this).runWithLooper(() -> {
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mQsPanel = new QSPanel(mContext, null);
+ mHost = mock(QSTileHost.class);
+ when(mHost.getTiles()).thenReturn(Collections.emptyList());
+ mCustomizer = mock(QSCustomizer.class);
+ mQsPanel.setHost(mHost, mCustomizer);
+ });
+ }
+
+ @Test
+ public void testSetExpanded_Metrics() {
+ mQsPanel.setExpanded(true);
+ verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(true));
+ mQsPanel.setExpanded(false);
+ verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
new file mode 100644
index 0000000..9ed9d28
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.qs.tileimpl;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.metrics.LogMaker;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QSTileHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSTileImplTest extends SysuiTestCase {
+
+ public static final int POSITION = 14;
+ private TestableLooper mTestableLooper;
+ private TileImpl mTile;
+ private QSTileHost mHost;
+ private MetricsLogger mMetricsLogger;
+
+ @Before
+ public void setup() throws Exception {
+ String spec = "spec";
+ mTestableLooper = TestableLooper.get(this);
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mHost = mock(QSTileHost.class);
+ when(mHost.indexOf(spec)).thenReturn(POSITION);
+ mTestableLooper.runWithLooper(() -> {
+ mTile = new TileImpl(mHost);
+ mTile.setTileSpec(spec);
+ });
+ }
+
+ @Test
+ public void testClick_Metrics() {
+ mTile.click();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
+ }
+
+ @Test
+ public void testSecondaryClick_Metrics() {
+ mTile.secondaryClick();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
+ }
+
+ @Test
+ public void testLongClick_Metrics() {
+ mTile.longClick();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
+ }
+
+ @Test
+ public void testPopulate() {
+ LogMaker maker = mock(LogMaker.class);
+ when(maker.setSubtype(anyInt())).thenReturn(maker);
+ mTile.getState().value = true;
+ mTile.populate(maker);
+ verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
+ verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
+ }
+
+ private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
+
+ private final int mCategory;
+ public String mInvalid;
+
+ public TileLogMatcher(int category) {
+ mCategory = category;
+ }
+
+ @Override
+ public boolean matches(LogMaker arg) {
+ if (arg.getCategory() != mCategory) {
+ mInvalid = "Expected category " + mCategory + " but was " + arg.getCategory();
+ return false;
+ }
+ if (arg.getType() != TYPE_ACTION) {
+ mInvalid = "Expected type " + TYPE_ACTION + " but was " + arg.getType();
+ return false;
+ }
+ if (arg.getSubtype() != mTile.getMetricsCategory()) {
+ mInvalid = "Expected subtype " + mTile.getMetricsCategory() + " but was "
+ + arg.getSubtype();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return mInvalid;
+ }
+ }
+
+ private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
+ protected TileImpl(QSHost host) {
+ super(host);
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 42;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ protected void setListening(boolean listening) {
+
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return null;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f48af75..bf6b394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -62,9 +62,9 @@
mKeyguardIndicationController = mock(KeyguardIndicationController.class);
mStackScroller = mock(NotificationStackScrollLayout.class);
mMetricsLogger = new FakeMetricsLogger();
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
mKeyguardIndicationController, mStackScroller);
- mStatusBar.setMetricsLogger(mMetricsLogger);
doAnswer(invocation -> {
OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 57d2581..da441f5 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -45,16 +45,16 @@
// The view or control was updated.
TYPE_UPDATE = 6;
- // Type for APP_TRANSITION event: The transition started a new activity for which it's process
- // wasn't running.
+ // Type for APP_TRANSITION event: The transition started a new
+ // activity for which it's process wasn't running.
TYPE_TRANSITION_COLD_LAUNCH = 7;
- // Type for APP_TRANSITION event: The transition started a new activity for which it's process
- // was already running.
+ // Type for APP_TRANSITION event: The transition started a new
+ // activity for which it's process was already running.
TYPE_TRANSITION_WARM_LAUNCH = 8;
- // Type for APP_TRANSITION event: The transition brought an already existing activity to the
- // front.
+ // Type for APP_TRANSITION event: The transition brought an
+ // already existing activity to the front.
TYPE_TRANSITION_HOT_LAUNCH = 9;
// The action was successful
@@ -64,6 +64,80 @@
TYPE_FAILURE = 11;
}
+ // Types of alerts, as bit field values
+ enum Alert {
+ // Vibrate the device.
+ ALERT_BUZZ = 1;
+
+ // Make sound through the speaker.
+ ALERT_BEEP = 2;
+
+ // Flash a notificaiton light.
+ ALERT_BLINK = 4;
+ }
+
+ // Reasons that a notification might be dismissed.
+ enum DismissReason {
+ // from android.service.notification.NotificationListenerService
+
+ // Notification was canceled by the status bar reporting a notification click
+ REASON_CLICK = 1;
+
+ // Notification was canceled by the status bar reporting a user dismissal.
+ REASON_CANCEL = 2;
+
+ // Notification was canceled by the status bar reporting a user dismiss all.
+ REASON_CANCEL_ALL = 3;
+
+ // Notification was canceled by the status bar reporting an inflation error.
+ REASON_ERROR = 4;
+
+ // Notification was canceled by the package manager modifying the package.
+ REASON_PACKAGE_CHANGED = 5;
+
+ // Notification was canceled by the owning user context being stopped.
+ REASON_USER_STOPPED = 6;
+
+ // Notification was canceled by the user banning the package.
+ REASON_PACKAGE_BANNED = 7;
+
+ // Notification was canceled by the app canceling this specific notification.
+ REASON_APP_CANCEL = 8;
+
+ //Notification was canceled by the app cancelling all its notifications.
+ REASON_APP_CANCEL_ALL = 9;
+
+ // Notification was canceled by a listener reporting a user dismissal.
+ REASON_LISTENER_CANCEL = 10;
+
+ //Notification was canceled by a listener reporting a user dismiss all.
+ REASON_LISTENER_CANCEL_ALL = 11;
+
+ // Notification was canceled because it was a member of a canceled group.
+ REASON_GROUP_SUMMARY_CANCELED = 12;
+
+ // Notification was canceled because it was an invisible member of a group.
+ REASON_GROUP_OPTIMIZATION = 13;
+
+ // Notification was canceled by the device administrator suspending the package.
+ REASON_PACKAGE_SUSPENDED = 14;
+
+ // Notification was canceled by the owning managed profile being turned off.
+ REASON_PROFILE_TURNED_OFF = 15;
+
+ // Autobundled summary notification was canceled because its group was unbundled.
+ REASON_UNAUTOBUNDLED = 16;
+
+ // Notification was canceled by the user banning the channel.
+ REASON_CHANNEL_BANNED = 17;
+
+ // Notification was snoozed.
+ REASON_SNOOZED = 18;
+
+ // Notification was canceled due to timeout.
+ REASON_TIMEOUT = 19;
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -97,7 +171,9 @@
// OS: 6.0
ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE = 6;
- // OPEN: Settings > Accessibility > Magnification gestures
+ // OPEN: Settings > Accessibility > Magnification gestures (Renamed in O)
+ // OPEN: Settings > Accessibility > Magnification > Magnify with triple-tap
+ // OPEN: Settings > Accessibility > Magnification > Magnify with button
// CATEGORY: SETTINGS
// OS: 6.0
ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 7;
@@ -1881,7 +1957,9 @@
// OS: N
SUW_ACCESSIBILITY = 367;
- // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gesture
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gestures (Renamed in O)
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification -> Magnify with triple-tap
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification -> Magnify with button
// ACTION: New magnification gesture configuration is chosen
// SUBTYPE: 0 is off, 1 is on
// CATEGORY: SETTINGS
@@ -3578,7 +3656,7 @@
ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE = 870;
// The name of the activity being launched in an app transition event.
- APP_TRANSITION_ACTIVITY_NAME = 871;
+ FIELD_CLASS_NAME = 871;
// ACTION: Settings > App detail > Uninstall
ACTION_SETTINGS_UNINSTALL_APP = 872;
@@ -3783,6 +3861,37 @@
// OPEN: Settings -> Display -> When in VR Mode
VR_DISPLAY_PREFERENCE = 921;
+ // OPEN: Settings > Accessibility > Magnification
+ // CATEGORY: SETTINGS
+ // OS: O
+ ACCESSIBILITY_SCREEN_MAGNIFICATION_SETTINGS = 922;
+
+ // ACTION: Logs pressing the "Clear app" button in the app info settings page for an instant
+ // app.
+ // VALUE: The package name of the app
+ ACTION_SETTINGS_CLEAR_INSTANT_APP = 923;
+
+ // OPEN: Settings -> System -> Reset options
+ RESET_DASHBOARD = 924;
+
+ // ACTION: QS -> Tile clicked
+ ACTION_QS_CLICK = 925;
+
+ // ACTION: QS -> Secondary click
+ ACTION_QS_SECONDARY_CLICK = 926;
+
+ // FIELD: Position info in QS clicks
+ FIELD_QS_POSITION = 927;
+
+ // FIELD: The value of a QS tile when clicked (if applicable)
+ FIELD_QS_VALUE = 928;
+
+ // ACTION: QS -> Detail panel -> more settings
+ ACTION_QS_MORE_SETTINGS = 929;
+
+ // ACTION: QS -> Click date
+ ACTION_QS_DATE = 930;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 397938a..05c6592 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -70,6 +70,7 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.provider.SettingsStringUtil.ComponentNameSet;
import android.provider.SettingsStringUtil.SettingStringHelper;
import android.text.TextUtils;
@@ -100,7 +101,6 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
@@ -1154,17 +1154,55 @@
private void notifyAccessibilityButtonClickedLocked() {
final UserState state = getCurrentUserStateLocked();
- if (state.mIsNavBarMagnificationEnabled) {
- mMainHandler.obtainMessage(
- MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
- // TODO(b/34720082): Only notify a single user-defined service
- if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonClickedLocked();
+
+ int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ potentialTargets++;
+ }
+ }
+
+ if (potentialTargets == 0) {
+ return;
+ }
+ if (potentialTargets == 1) {
+ if (state.mIsNavBarMagnificationEnabled) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
+ return;
+ } else {
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ service.notifyAccessibilityButtonClickedLocked();
+ return;
+ }
}
}
+ } else {
+ if (state.mServiceAssignedToAccessibilityButton == null
+ && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget();
+ } else if (state.mIsNavBarMagnificationEnabled
+ && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
+ return;
+ } else {
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
+ state.mServiceAssignedToAccessibilityButton))) {
+ service.notifyAccessibilityButtonClickedLocked();
+ return;
+ }
+ }
+ }
+ // The user may have turned off the assigned service or feature
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget();
}
}
@@ -1534,6 +1572,12 @@
}
}
+ private void showAccessibilityButtonTargetSelection() {
+ Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+ }
+
private void scheduleNotifyClientsOfServicesStateChange(UserState userState) {
mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS,
userState.mUserId).sendToTarget();
@@ -1681,6 +1725,7 @@
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
updateRelevantEventsLocked(userState);
+ updateAccessibilityButtonTargets(userState);
}
private void updateAccessibilityFocusBehaviorLocked(UserState userState) {
@@ -1794,6 +1839,7 @@
somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
somethingChanged |= readAccessibilityShortcutSettingLocked(userState);
+ somethingChanged |= readAccessibilityButtonSettingsLocked(userState);
return somethingChanged;
}
@@ -1920,13 +1966,45 @@
}
ComponentName componentNameToEnable =
ComponentName.unflattenFromString(componentNameToEnableString);
- if (componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
+ if ((componentNameToEnable != null)
+ && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
return false;
}
userState.mServiceToEnableWithShortcut = componentNameToEnable;
return true;
}
+ private boolean readAccessibilityButtonSettingsLocked(UserState userState) {
+ String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
+ if (TextUtils.isEmpty(componentId)) {
+ if ((userState.mServiceAssignedToAccessibilityButton == null)
+ && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ return false;
+ }
+ userState.mServiceAssignedToAccessibilityButton = null;
+ userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ return true;
+ }
+
+ if (componentId.equals(MagnificationController.class.getName())) {
+ if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ return false;
+ }
+ userState.mServiceAssignedToAccessibilityButton = null;
+ userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true;
+ return true;
+ }
+
+ ComponentName componentName = ComponentName.unflattenFromString(componentId);
+ if (componentName.equals(userState.mServiceAssignedToAccessibilityButton)) {
+ return false;
+ }
+ userState.mServiceAssignedToAccessibilityButton = componentName;
+ userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ return true;
+ }
+
/**
* Check if the service that will be enabled by the shortcut is installed. If it isn't,
* clear the value and the associated setting so a sideloaded service can't spoof the
@@ -1948,7 +2026,9 @@
if (!shortcutServiceIsInstalled) {
userState.mServiceToEnableWithShortcut = null;
Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", userState.mUserId);
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, userState.mUserId);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId);
}
}
@@ -2135,6 +2215,22 @@
}
}
+ private void updateAccessibilityButtonTargets(UserState userState) {
+ final List<Service> services;
+ synchronized (mLock) {
+ services = userState.mBoundServices;
+ int numServices = services.size();
+ for (int i = 0; i < numServices; i++) {
+ final Service service = services.get(i);
+ if (service.mRequestAccessibilityButton) {
+ boolean available = service.mComponentName.equals(
+ userState.mServiceAssignedToAccessibilityButton);
+ service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ }
+ }
+ }
+ }
+
@GuardedBy("mLock")
private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
@@ -2209,7 +2305,7 @@
* Disables accessibility service specified by {@param componentName} for the {@param userId}.
*/
private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) {
- final SettingStringHelper setting =
+ final SettingsStringUtil.SettingStringHelper setting =
new SettingStringHelper(
mContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
@@ -2339,6 +2435,7 @@
public static final int MSG_UPDATE_FINGERPRINT = 11;
public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12;
public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13;
+ public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14;
public MainHandler(Looper looper) {
super(looper);
@@ -2432,6 +2529,10 @@
mInputFilter.notifyAccessibilityButtonClicked();
}
}
+ } break;
+
+ case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: {
+ showAccessibilityButtonTargetSelection();
}
}
}
@@ -4810,6 +4911,8 @@
public int mSoftKeyboardShowMode = 0;
public boolean mIsAccessibilityButtonAvailable;
+ public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
+ public ComponentName mServiceAssignedToAccessibilityButton;
public boolean mIsTouchExplorationEnabled;
public boolean mIsTextHighContrastEnabled;
@@ -4885,6 +4988,8 @@
mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
mIsNavBarMagnificationEnabled = false;
+ mServiceAssignedToAccessibilityButton = null;
+ mIsNavBarMagnificationAssignedToAccessibilityButton = false;
mIsAutoclickEnabled = false;
mSoftKeyboardShowMode = 0;
@@ -4950,6 +5055,9 @@
private final Uri mAccessibilityShortcutServiceIdUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+ private final Uri mAccessibilityButtonComponentIdUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4982,6 +5090,8 @@
mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -5039,6 +5149,10 @@
if (readAccessibilityShortcutSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilityButtonComponentIdUri.equals(uri)) {
+ if (readAccessibilityButtonSettingsLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index a77e533..72d37ad 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -365,7 +365,6 @@
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
- bounds = Preconditions.checkNotNull(bounds, "bounds");
packageName = Preconditions.checkNotNull(packageName, "packageName");
Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 6fb9f7c..4d78350 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -71,8 +71,7 @@
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
-import android.view.autofill.AutofillManager.AutofillCallback;
-
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -298,17 +297,17 @@
}
void startSessionLocked(@NonNull IBinder activityToken, @Nullable IBinder windowToken,
- @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect bounds,
- @Nullable AutofillValue value, boolean hasCallback, int flags,
- @NonNull String packageName) {
+ @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
+ @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
+ int flags, @NonNull String packageName) {
if (!isEnabled()) {
return;
}
final String historyItem = "s=" + mInfo.getServiceInfo().packageName
+ " u=" + mUserId + " a=" + activityToken
-
- + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback + " f=" + flags;
+ + " i=" + autofillId + " b=" + virtualBounds + " hc=" + hasCallback
+ + " f=" + flags;
mRequestsHistory.log(historyItem);
// TODO(b/33197203): Handle partitioning
@@ -320,7 +319,7 @@
final Session newSession = createSessionByTokenLocked(activityToken,
windowToken, appCallbackToken, hasCallback, flags, packageName);
- newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION);
+ newSession.updateLocked(autofillId, virtualBounds, value, FLAG_START_SESSION);
}
void finishSessionLocked(IBinder activityToken) {
@@ -388,7 +387,7 @@
return newSession;
}
- void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect bounds,
+ void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect virtualBounds,
AutofillValue value, int flags) {
final Session session = mSessions.get(activityToken);
if (session == null) {
@@ -398,7 +397,7 @@
return;
}
- session.updateLocked(autofillId, bounds, value, flags);
+ session.updateLocked(autofillId, virtualBounds, value, flags);
}
private void handleSessionSave(IBinder activityToken) {
@@ -508,8 +507,8 @@
/**
* Called when the fill UI is ready to be shown for this view.
*/
- void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds,
- AutofillId focusedId, @Nullable AutofillValue value);
+ void onFillReady(FillResponse fillResponse, AutofillId focusedId,
+ @Nullable AutofillValue value);
}
final AutofillId mId;
@@ -522,7 +521,9 @@
Intent mAuthIntent;
private AutofillValue mAutofillValue;
- private Rect mBounds;
+
+ // Bounds if a virtual view, null otherwise
+ private Rect mVirtualBounds;
private boolean mValueUpdated;
@@ -555,12 +556,12 @@
// TODO(b/33197203): need to refactor / rename / document this method to make it clear that
// it can change the value and update the UI; similarly, should replace code that
// directly sets mAutoFilLValue to use encapsulation.
- void update(@Nullable AutofillValue autofillValue, @Nullable Rect bounds) {
+ void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) {
if (autofillValue != null) {
mAutofillValue = autofillValue;
}
- if (bounds != null) {
- mBounds = bounds;
+ if (virtualBounds != null) {
+ mVirtualBounds = virtualBounds;
}
maybeCallOnFillReady();
@@ -568,19 +569,19 @@
/**
* Calls {@link
- * Listener#onFillReady(ViewState, FillResponse, Rect, AutofillId, AutofillValue)} if the
+ * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the
* fill UI is ready to be displayed (i.e. when response and bounds are set).
*/
void maybeCallOnFillReady() {
if (mResponse != null && (mResponse.getAuthentication() != null
- || mResponse.getDatasets() != null) && mBounds != null) {
- mListener.onFillReady(this, mResponse, mBounds, mId, mAutofillValue);
+ || mResponse.getDatasets() != null)) {
+ mListener.onFillReady(mResponse, mId, mAutofillValue);
}
}
@Override
public String toString() {
- return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mBounds
+ return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds
+ ", updated = " + mValueUpdated + "]";
}
@@ -588,7 +589,7 @@
pw.print(prefix); pw.print("id:" ); pw.println(mId);
pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue);
pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
- pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
+ pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
}
}
@@ -817,8 +818,24 @@
// AutoFillUiCallback
@Override
- public void onEvent(AutofillId id, int event) {
- mHandlerCaller.getHandler().post(() -> notifyChangeToClient(id, event));
+ public void requestShowFillUi(AutofillId id, int width, int height,
+ IAutofillWindowPresenter presenter) {
+ try {
+ mClient.requestShowFillUi(mWindowToken, id, width, height,
+ mCurrentViewState.mVirtualBounds, presenter);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting to show fill UI", e);
+ }
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void requestHideFillUi(AutofillId id) {
+ try {
+ mClient.requestHideFillUi(mWindowToken, id);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting to hide fill UI", e);
+ }
}
public void setAuthenticationResultLocked(Bundle data) {
@@ -835,10 +852,11 @@
processResponseLocked(mCurrentResponse);
} else if (result instanceof Dataset) {
Dataset dataset = (Dataset) result;
- mCurrentResponse.getDatasets().remove(mAutoFilledDataset);
- mCurrentResponse.getDatasets().add(dataset);
- mAutoFilledDataset = dataset;
- processResponseLocked(mCurrentResponse);
+ final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset);
+ if (index >= 0) {
+ mCurrentResponse.getDatasets().set(index, dataset);
+ autoFill(dataset);
+ }
}
}
}
@@ -1009,7 +1027,7 @@
mRemoteFillService.onSaveRequest(mStructure, extras);
}
- void updateLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) {
+ void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
// TODO(b/33197203): ignoring because we don't support partitions yet
Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled");
@@ -1025,7 +1043,7 @@
if ((flags & FLAG_START_SESSION) != 0) {
// View is triggering autofill.
mCurrentViewState = viewState;
- viewState.update(value, bounds);
+ viewState.update(value, virtualBounds);
return;
}
@@ -1065,7 +1083,7 @@
}
// If the ViewState is ready to be displayed, onReady() will be called.
- viewState.update(value, bounds);
+ viewState.update(value, virtualBounds);
// TODO(b/33197203): Remove when there is a response per activity.
if (mCurrentResponse != null) {
@@ -1087,23 +1105,14 @@
}
@Override
- public void onFillReady(ViewState viewState, FillResponse response, Rect bounds,
- AutofillId filledId, @Nullable AutofillValue value) {
+ public void onFillReady(FillResponse response, AutofillId filledId,
+ @Nullable AutofillValue value) {
String filterText = null;
if (value != null && value.isText()) {
filterText = value.getTextValue().toString();
}
- getUiForShowing().showFillUi(filledId, response, bounds, filterText, mPackageName);
- }
-
- private void notifyChangeToClient(AutofillId id, int event) {
- if (!mHasCallback) return;
- try {
- mClient.onAutofillEvent(mWindowToken, id, event);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error notifying client on change: id=" + id + ", event=" + event, e);
- }
+ getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
}
private void notifyUnavailableToClient() {
@@ -1112,7 +1121,13 @@
Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
return;
}
- notifyChangeToClient(mCurrentViewState.mId, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ if (!mHasCallback) return;
+ try {
+ mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
+ + " id=" + mCurrentViewState.mId, e);
+ }
}
private void processResponseLocked(FillResponse response) {
@@ -1212,7 +1227,7 @@
if (DEBUG) {
Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
}
- mClient.autofill(dataset.getFieldIds(), dataset.getFieldValues());
+ mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
} catch (RemoteException e) {
Slog.w(TAG, "Error autofilling activity: " + e);
}
@@ -1221,7 +1236,7 @@
private AutoFillUI getUiForShowing() {
synchronized (mLock) {
- mUi.setCallback(this, mWindowToken);
+ mUi.setCallback(this);
return mUi;
}
}
@@ -1261,8 +1276,7 @@
private void destroyLocked() {
mRemoteFillService.destroy();
- mUi.setCallback(null, null);
-
+ mUi.setCallback(null);
mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED,
mPackageName);
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 299b456..003c8f1 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -43,7 +43,6 @@
import com.android.internal.os.HandlerCaller;
import com.android.server.FgThread;
-import com.android.server.autofill.AutofillManagerServiceImpl.Session;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -89,10 +88,13 @@
private PendingRequest mPendingRequest;
public interface FillServiceCallbacks {
- void onFillRequestSuccess(@Nullable FillResponse response, @NonNull String servicePackageName);
- void onFillRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName);
+ void onFillRequestSuccess(@Nullable FillResponse response,
+ @NonNull String servicePackageName);
+ void onFillRequestFailure(@Nullable CharSequence message,
+ @NonNull String servicePackageName);
void onSaveRequestSuccess(@NonNull String servicePackageName);
- void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName);
+ void onSaveRequestFailure(@Nullable CharSequence message,
+ @NonNull String servicePackageName);
void onServiceDied(RemoteFillService service);
void onDisableSelf();
}
@@ -243,14 +245,10 @@
}
mBinding = false;
if (isBound()) {
- // TODO(b/33197203): synchronize access instead?
- // Need to double check if it's null, since it could be set on onServiceDisconnected()
- if (mAutoFillService != null) {
- try {
- mAutoFillService.onInit(null);
- } catch (Exception e) {
- Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
- }
+ try {
+ mAutoFillService.onInit(null);
+ } catch (Exception e) {
+ Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
}
if (mAutoFillService != null) {
mAutoFillService.asBinder().unlinkToDeath(this, 0);
@@ -323,19 +321,13 @@
handleBinderDied();
return;
}
-
try {
- // TODO(b/33197203): synchronize access instead?
- // Need to double check if it's null, since it could be set on
- // onServiceDisconnected()
- if (mAutoFillService != null) {
- mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() {
- @Override
- public void disableSelf() {
- mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget();
- }
- });
- }
+ mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() {
+ @Override
+ public void disableSelf() {
+ mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget();
+ }
+ });
} catch (RemoteException e) {
Slog.w(LOG_TAG, "Exception calling onConnected(): " + e);
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 375a726..2555cee 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -15,19 +15,14 @@
*/
package com.android.server.autofill.ui;
-import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
-import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
-
import static com.android.server.autofill.ui.Helper.DEBUG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.IntentSender;
-import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Handler;
-import android.os.IBinder;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveInfo;
@@ -35,6 +30,7 @@
import android.text.format.DateUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
+import android.view.autofill.IAutofillWindowPresenter;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
@@ -62,7 +58,6 @@
private @Nullable SaveUi mSaveUi;
private @Nullable AutoFillUiCallback mCallback;
- private @Nullable IBinder mWindowToken;
private int mSaveTimeoutMs = (int) (5 * DateUtils.SECOND_IN_MILLIS);
private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -72,20 +67,20 @@
void fill(@NonNull Dataset dataset);
void save();
void cancelSave();
- void onEvent(AutofillId id, int event);
+ void requestShowFillUi(AutofillId id, int width, int height,
+ IAutofillWindowPresenter presenter);
+ void requestHideFillUi(AutofillId id);
}
public AutoFillUI(@NonNull Context context) {
mContext = context;
}
- public void setCallback(@Nullable AutoFillUiCallback callback,
- @Nullable IBinder windowToken) {
+ public void setCallback(@Nullable AutoFillUiCallback callback) {
mHandler.post(() -> {
- if (mCallback != callback || mWindowToken != windowToken) {
+ if (mCallback != callback) {
hideAllUiThread();
mCallback = callback;
- mWindowToken = windowToken;
}
});
}
@@ -109,12 +104,7 @@
* Hides the fill UI.
*/
public void hideFillUi(AutofillId id) {
- mHandler.post(() -> {
- hideFillUiUiThread();
- if (mCallback != null) {
- mCallback.onEvent(id, EVENT_INPUT_HIDDEN);
- }
- });
+ mHandler.post(this::hideFillUiUiThread);
}
/**
@@ -135,36 +125,17 @@
}
/**
- * Updates the position of the fill UI.
- *
- * @param anchoredBounds The bounds of the anchor view.
- */
- public void updateFillUi(@NonNull Rect anchoredBounds) {
- mHandler.post(() -> {
- if (!hasCallback()) {
- return;
- }
- hideSaveUiUiThread();
- if (mFillUi != null) {
- mFillUi.update(anchoredBounds);
- }
- });
- }
-
- /**
* Shows the fill UI, removing the previous fill UI if the has changed.
*
* @param focusedId the currently focused field
* @param response the current fill response
- * @param anchorBounds bounds of the focused view
* @param filterText text of the view to be filled
* @param packageName package name of the activity that is filled
*/
public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
- @NonNull Rect anchorBounds, @Nullable String filterText, @NonNull String packageName) {
+ @Nullable String filterText, @NonNull String packageName) {
if (DEBUG) {
- Slog.d(TAG, "showFillUi(): id=" + focusedId + ", bounds=" + anchorBounds + " filter="
- + filterText);
+ Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + filterText);
}
final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
.setPackageName(packageName)
@@ -179,7 +150,7 @@
}
hideAllUiThread();
mFillUi = new FillUi(mContext, response, focusedId,
- mWindowToken, anchorBounds, filterText, new FillUi.Callback() {
+ filterText, new FillUi.Callback() {
@Override
public void onResponsePicked(FillResponse response) {
log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
@@ -211,8 +182,22 @@
}
mMetricsLogger.write(log);
}
+
+ @Override
+ public void requestShowFillUi(int width, int height,
+ IAutofillWindowPresenter windowPresenter) {
+ if (mCallback != null) {
+ mCallback.requestShowFillUi(focusedId, width, height, windowPresenter);
+ }
+ }
+
+ @Override
+ public void requestHideFillUi() {
+ if (mCallback != null) {
+ mCallback.requestHideFillUi(focusedId);
+ }
+ }
});
- mCallback.onEvent(focusedId, EVENT_INPUT_SHOWN);
});
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 98a02f2..d38fb96 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -18,14 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.graphics.Rect;
-import android.os.IBinder;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.util.Slog;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -34,11 +30,13 @@
import android.view.WindowManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.RemoteViews;
import com.android.internal.R;
+import com.android.server.UiThread;
import libcore.util.Objects;
import java.io.PrintWriter;
@@ -54,9 +52,13 @@
void onDatasetPicked(@NonNull Dataset dataset);
void onCanceled();
void onDestroy();
+ void requestShowFillUi(int width, int height,
+ IAutofillWindowPresenter windowPresenter);
+ void requestHideFillUi();
}
- private final Rect mAnchorBounds = new Rect();
+ private final @NonNull AutofillWindowPresenter mWindowPresenter =
+ new AutofillWindowPresenter();
private final @NonNull AnchoredWindow mWindow;
@@ -75,10 +77,8 @@
private boolean mDestroyed;
FillUi(@NonNull Context context, @NonNull FillResponse response,
- @NonNull AutofillId focusedViewId, @NonNull IBinder windowToken,
- @NonNull Rect anchorBounds, @Nullable String filterText,
+ @NonNull AutofillId focusedViewId, @NonNull @Nullable String filterText,
@NonNull Callback callback) {
- mAnchorBounds.set(anchorBounds);
mCallback = callback;
mAccessibilityTitle = context.getString(R.string.autofill_picker_accessibility_title);
@@ -104,8 +104,8 @@
mContentWidth = content.getMeasuredWidth();
mContentHeight = content.getMeasuredHeight();
- mWindow = new AnchoredWindow(windowToken, content);
- mWindow.show(mContentWidth, mContentHeight, mAnchorBounds);
+ mWindow = new AnchoredWindow(content);
+ mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
} else {
final int datasetCount = response.getDatasets().size();
final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
@@ -153,15 +153,7 @@
}
applyNewFilterText();
- mWindow = new AnchoredWindow(windowToken, mListView);
- }
- }
-
- public void update(@NonNull Rect anchorBounds) {
- throwIfDestroyed();
- if (!mAnchorBounds.equals(anchorBounds)) {
- mAnchorBounds.set(anchorBounds);
- mWindow.show(mContentWidth, mContentHeight, anchorBounds);
+ mWindow = new AnchoredWindow(mListView);
}
}
@@ -171,10 +163,10 @@
return;
}
if (count <= 0) {
- mWindow.hide();
+ mCallback.requestHideFillUi();
} else {
if (updateContentSize()) {
- mWindow.show(mContentWidth, mContentHeight, mAnchorBounds);
+ mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
}
if (mAdapter.getCount() > VISIBLE_OPTIONS_MAX_COUNT) {
mListView.setVerticalScrollBarEnabled(true);
@@ -209,7 +201,7 @@
public void destroy() {
throwIfDestroyed();
mCallback.onDestroy();
- mWindow.hide();
+ mCallback.requestHideFillUi();
mDestroyed = true;
}
@@ -285,33 +277,61 @@
}
}
+ private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub {
+ @Override
+ public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
+ boolean fitsSystemWindows, int layoutDirection) {
+ UiThread.getHandler().post(() -> mWindow.show(p));
+ }
+
+ @Override
+ public void hide(Rect transitionEpicenter) {
+ UiThread.getHandler().post(mWindow::hide);
+ }
+ }
+
final class AnchoredWindow implements View.OnTouchListener {
- private final Point mTempPoint = new Point();
-
private final WindowManager mWm;
-
- private final IBinder mActivityToken;
private final View mContentView;
+ private boolean mShowing;
/**
* Constructor.
*
- * @param activityToken token to pass to window manager
* @param contentView content of the window
*/
- AnchoredWindow(IBinder activityToken, View contentView) {
+ AnchoredWindow(View contentView) {
mWm = contentView.getContext().getSystemService(WindowManager.class);
- mActivityToken = activityToken;
mContentView = contentView;
}
/**
+ * Shows the window.
+ */
+ public void show(WindowManager.LayoutParams params) {
+ try {
+ if (!mShowing) {
+ params.accessibilityTitle = mAccessibilityTitle;
+ mWm.addView(mContentView, params);
+ mContentView.setOnTouchListener(this);
+ mShowing = true;
+ } else {
+ mWm.updateViewLayout(mContentView, params);
+ }
+ } catch (WindowManager.BadTokenException e) {
+ Slog.i(TAG, "Filed with with token " + params.token + " gone.");
+ mCallback.onDestroy();
+ }
+ }
+
+ /**
* Hides the window.
*/
void hide() {
- if (mContentView.isAttachedToWindow()) {
+ if (mShowing) {
mContentView.setOnTouchListener(null);
mWm.removeView(mContentView);
+ mShowing = false;
}
}
@@ -324,82 +344,9 @@
}
return false;
}
-
- public void show(int desiredWidth, int desiredHeight, Rect anchorBounds) {
- try {
- // TODO: temporary workaround to avoud system_server crashes.
- unsafelyShow(desiredWidth, desiredHeight, anchorBounds);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error showing Anchored window: w=" + desiredWidth + ", h="
- + desiredHeight + ", b=" + anchorBounds, e);
- }
- }
-
- private void unsafelyShow(int desiredWidth, int desiredHeight, Rect anchorBounds) {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-
- params.setTitle("FillUi");
- params.token = mActivityToken;
- params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
- params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
- params.accessibilityTitle = mAccessibilityTitle;
-
- mWm.getDefaultDisplay().getRealSize(mTempPoint);
- final int screenWidth = mTempPoint.x;
- final int screenHeight = mTempPoint.y;
-
- // Try to place the window at the start of the anchor view if
- // there is space to fit the content, otherwise fit as much of
- // the window as possible moving it to the left using all available
- // screen width.
- params.x = Math.min(anchorBounds.left, Math.max(screenWidth - desiredWidth, 0));
- params.width = Math.min(screenWidth, desiredWidth);
-
- // Try to fit below using all available space with top-start gravity
- // and if that fails try to fit above using all available space with
- // bottom-start gravity.
- final int verticalSpaceBelow = screenHeight - anchorBounds.bottom;
- if (desiredHeight <= verticalSpaceBelow) {
- // Fits below bounds.
- params.height = desiredHeight;
- params.gravity = Gravity.TOP | Gravity.START;
- params.y = anchorBounds.bottom;
- } else {
- final int verticalSpaceAbove = anchorBounds.top;
- if (desiredHeight <= verticalSpaceAbove) {
- // Fits above bounds.
- params.height = desiredHeight;
- params.gravity = Gravity.BOTTOM | Gravity.START;
- params.y = anchorBounds.top + desiredHeight;
- } else {
- // Pick above/below based on which has the most space.
- if (verticalSpaceBelow >= verticalSpaceAbove) {
- params.height = verticalSpaceBelow;
- params.gravity = Gravity.TOP | Gravity.START;
- params.y = anchorBounds.bottom;
- } else {
- params.height = verticalSpaceAbove;
- params.gravity = Gravity.BOTTOM | Gravity.START;
- params.y = anchorBounds.top + desiredHeight;
- }
- }
- }
-
- if (!mContentView.isAttachedToWindow()) {
- mWm.addView(mContentView, params);
- mContentView.setOnTouchListener(this);
- } else {
- mWm.updateViewLayout(mContentView, params);
- }
- }
}
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mAnchorBounds: "); pw.println(mAnchorBounds);
pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 30d06db..037804e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -222,12 +222,27 @@
// 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
// 3 : introduced "_meta" metadata file; no other format change per se
// 4 : added support for new device-encrypted storage locations
- static final int BACKUP_FILE_VERSION = 4;
+ // 5 : added support for key-value packages
+ static final int BACKUP_FILE_VERSION = 5;
static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
static final int BACKUP_PW_FILE_VERSION = 2;
static final String BACKUP_METADATA_FILENAME = "_meta";
static final int BACKUP_METADATA_VERSION = 1;
static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
+
+ static final int TAR_HEADER_LONG_RADIX = 8;
+ static final int TAR_HEADER_OFFSET_FILESIZE = 124;
+ static final int TAR_HEADER_LENGTH_FILESIZE = 12;
+ static final int TAR_HEADER_OFFSET_MODTIME = 136;
+ static final int TAR_HEADER_LENGTH_MODTIME = 12;
+ static final int TAR_HEADER_OFFSET_MODE = 100;
+ static final int TAR_HEADER_LENGTH_MODE = 8;
+ static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
+ static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
+ static final int TAR_HEADER_OFFSET_PATH = 0;
+ static final int TAR_HEADER_LENGTH_PATH = 100;
+ static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
+
static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
static final String SETTINGS_PACKAGE = "com.android.providers.settings";
@@ -553,19 +568,20 @@
}
}
- class FullParams {
+ // Parameters used by adbBackup() and adbRestore()
+ class AdbParams {
public ParcelFileDescriptor fd;
public final AtomicBoolean latch;
public IFullBackupRestoreObserver observer;
public String curPassword; // filled in by the confirmation step
public String encryptPassword;
- FullParams() {
+ AdbParams() {
latch = new AtomicBoolean(false);
}
}
- class FullBackupParams extends FullParams {
+ class AdbBackupParams extends AdbParams {
public boolean includeApks;
public boolean includeObbs;
public boolean includeShared;
@@ -573,11 +589,12 @@
public boolean allApps;
public boolean includeSystem;
public boolean doCompress;
+ public boolean includeKeyValue;
public String[] packages;
- FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
+ AdbBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
- boolean compress, String[] pkgList) {
+ boolean compress, boolean doKeyValue, String[] pkgList) {
fd = output;
includeApks = saveApks;
includeObbs = saveObbs;
@@ -586,12 +603,13 @@
allApps = doAllApps;
includeSystem = doSystem;
doCompress = compress;
+ includeKeyValue = doKeyValue;
packages = pkgList;
}
}
- class FullRestoreParams extends FullParams {
- FullRestoreParams(ParcelFileDescriptor input) {
+ class AdbRestoreParams extends AdbParams {
+ AdbRestoreParams(ParcelFileDescriptor input) {
fd = input;
}
}
@@ -627,10 +645,10 @@
static final int OP_TIMEOUT = -1;
// Waiting for backup agent to respond during backup operation.
- private static final int OP_TYPE_BACKUP_WAIT = 0;
+ static final int OP_TYPE_BACKUP_WAIT = 0;
// Waiting for backup agent to respond during restore operation.
- private static final int OP_TYPE_RESTORE_WAIT = 1;
+ static final int OP_TYPE_RESTORE_WAIT = 1;
// An entire backup operation spanning multiple packages.
private static final int OP_TYPE_BACKUP = 2;
@@ -672,7 +690,7 @@
final Object mCurrentOpLock = new Object();
final Random mTokenGenerator = new Random();
- final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>();
+ final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<AdbParams>();
// Where we keep our journal files and other bookkeeping
File mBaseStateDir;
@@ -791,15 +809,9 @@
}
/* adb backup: is this app only capable of doing key/value? We say otherwise if
- * the app has a backup agent and does not say fullBackupOnly, *unless* it
- * is a package that we know _a priori_ explicitly supports both key/value and
- * full-data backup.
+ * the app has a backup agent and does not say fullBackupOnly,
*/
private static boolean appIsKeyValueOnly(PackageInfo pkg) {
- if ("com.android.providers.settings".equals(pkg.packageName)) {
- return false;
- }
-
return !appGetsFullBackup(pkg);
}
@@ -912,13 +924,12 @@
{
// TODO: refactor full backup to be a looper-based state machine
// similar to normal backup/restore.
- FullBackupParams params = (FullBackupParams)msg.obj;
+ AdbBackupParams params = (AdbBackupParams)msg.obj;
PerformAdbBackupTask task = new PerformAdbBackupTask(params.fd,
params.observer, params.includeApks, params.includeObbs,
- params.includeShared, params.doWidgets,
- params.curPassword, params.encryptPassword,
- params.allApps, params.includeSystem, params.doCompress,
- params.packages, params.latch);
+ params.includeShared, params.doWidgets, params.curPassword,
+ params.encryptPassword, params.allApps, params.includeSystem,
+ params.doCompress, params.includeKeyValue, params.packages, params.latch);
(new Thread(task, "adb-backup")).start();
break;
}
@@ -963,7 +974,7 @@
{
// TODO: refactor full restore to be a looper-based state machine
// similar to normal backup/restore.
- FullRestoreParams params = (FullRestoreParams)msg.obj;
+ AdbRestoreParams params = (AdbRestoreParams)msg.obj;
PerformAdbRestoreTask task = new PerformAdbRestoreTask(params.fd,
params.curPassword, params.encryptPassword,
params.observer, params.latch);
@@ -1071,16 +1082,16 @@
case MSG_FULL_CONFIRMATION_TIMEOUT:
{
- synchronized (mFullConfirmations) {
- FullParams params = mFullConfirmations.get(msg.arg1);
+ synchronized (mAdbBackupRestoreConfirmations) {
+ AdbParams params = mAdbBackupRestoreConfirmations.get(msg.arg1);
if (params != null) {
Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation");
// Release the waiter; timeout == completion
- signalFullBackupRestoreCompletion(params);
+ signalAdbBackupRestoreCompletion(params);
// Remove the token from the set
- mFullConfirmations.delete(msg.arg1);
+ mAdbBackupRestoreConfirmations.delete(msg.arg1);
// Report a timeout to the observer, if any
if (params.observer != null) {
@@ -3719,7 +3730,7 @@
}
- private void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
+ static void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
throws IOException {
// We do not take close() responsibility for the pipe FD
FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
@@ -3822,7 +3833,7 @@
if (mWriteManifest) {
final boolean writeWidgetData = mWidgetData != null;
if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
- writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData);
+ writeAppManifest(mPackage, mPackageManager, mManifestFile, mSendApk, writeWidgetData);
FullBackup.backupToTar(mPackage.packageName, null, null,
mFilesDir.getAbsolutePath(),
mManifestFile.getAbsolutePath(),
@@ -4006,52 +4017,6 @@
}
}
- private void writeAppManifest(PackageInfo pkg, File manifestFile,
- boolean withApk, boolean withWidgets) throws IOException {
- // Manifest format. All data are strings ending in LF:
- // BACKUP_MANIFEST_VERSION, currently 1
- //
- // Version 1:
- // package name
- // package's versionCode
- // platform versionCode
- // getInstallerPackageName() for this package (maybe empty)
- // boolean: "1" if archive includes .apk; any other string means not
- // number of signatures == N
- // N*: signature byte array in ascii format per Signature.toCharsString()
- StringBuilder builder = new StringBuilder(4096);
- StringBuilderPrinter printer = new StringBuilderPrinter(builder);
-
- printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
- printer.println(pkg.packageName);
- printer.println(Integer.toString(pkg.versionCode));
- printer.println(Integer.toString(Build.VERSION.SDK_INT));
-
- String installerName = mPackageManager.getInstallerPackageName(pkg.packageName);
- printer.println((installerName != null) ? installerName : "");
-
- printer.println(withApk ? "1" : "0");
- if (pkg.signatures == null) {
- printer.println("0");
- } else {
- printer.println(Integer.toString(pkg.signatures.length));
- for (Signature sig : pkg.signatures) {
- printer.println(sig.toCharsString());
- }
- }
-
- FileOutputStream outstream = new FileOutputStream(manifestFile);
- outstream.write(builder.toString().getBytes());
- outstream.close();
-
- // We want the manifest block in the archive stream to be idempotent:
- // each time we generate a backup stream for the app, we want the manifest
- // block to be identical. The underlying tar mechanism sees it as a file,
- // though, and will propagate its mtime, causing the tar header to vary.
- // Avoid this problem by pinning the mtime to zero.
- manifestFile.setLastModified(0);
- }
-
// Widget metadata format. All header entries are strings ending in LF:
//
// Version 1 header:
@@ -4100,6 +4065,52 @@
}
}
+ static void writeAppManifest(PackageInfo pkg, PackageManager packageManager, File manifestFile,
+ boolean withApk, boolean withWidgets) throws IOException {
+ // Manifest format. All data are strings ending in LF:
+ // BACKUP_MANIFEST_VERSION, currently 1
+ //
+ // Version 1:
+ // package name
+ // package's versionCode
+ // platform versionCode
+ // getInstallerPackageName() for this package (maybe empty)
+ // boolean: "1" if archive includes .apk; any other string means not
+ // number of signatures == N
+ // N*: signature byte array in ascii format per Signature.toCharsString()
+ StringBuilder builder = new StringBuilder(4096);
+ StringBuilderPrinter printer = new StringBuilderPrinter(builder);
+
+ printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
+ printer.println(pkg.packageName);
+ printer.println(Integer.toString(pkg.versionCode));
+ printer.println(Integer.toString(Build.VERSION.SDK_INT));
+
+ String installerName = packageManager.getInstallerPackageName(pkg.packageName);
+ printer.println((installerName != null) ? installerName : "");
+
+ printer.println(withApk ? "1" : "0");
+ if (pkg.signatures == null) {
+ printer.println("0");
+ } else {
+ printer.println(Integer.toString(pkg.signatures.length));
+ for (Signature sig : pkg.signatures) {
+ printer.println(sig.toCharsString());
+ }
+ }
+
+ FileOutputStream outstream = new FileOutputStream(manifestFile);
+ outstream.write(builder.toString().getBytes());
+ outstream.close();
+
+ // We want the manifest block in the archive stream to be idempotent:
+ // each time we generate a backup stream for the app, we want the manifest
+ // block to be identical. The underlying tar mechanism sees it as a file,
+ // though, and will propagate its mtime, causing the tar header to vary.
+ // Avoid this problem by pinning the mtime to zero.
+ manifestFile.setLastModified(0);
+ }
+
// Generic driver skeleton for full backup operations
abstract class FullBackupTask implements Runnable {
IFullBackupRestoreObserver mObserver;
@@ -4172,6 +4183,7 @@
boolean mAllApps;
boolean mIncludeSystem;
boolean mCompress;
+ boolean mKeyValue;
ArrayList<String> mPackages;
PackageInfo mCurrentTarget;
String mCurrentPassword;
@@ -4179,9 +4191,9 @@
private final int mCurrentOpToken;
PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
- boolean includeApks, boolean includeObbs, boolean includeShared,
- boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
- boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
+ boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
+ String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
+ boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
super(observer);
mCurrentOpToken = generateToken();
mLatch = latch;
@@ -4210,6 +4222,7 @@
Slog.w(TAG, "Encrypting backup with passphrase=" + mEncryptPassword);
}
mCompress = doCompress;
+ mKeyValue = doKeyValue;
}
void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
@@ -4309,7 +4322,8 @@
@Override
public void run() {
- Slog.i(TAG, "--- Performing full-dataset adb backup ---");
+ String includeKeyValue = mKeyValue ? ", including key-value backups" : "";
+ Slog.i(TAG, "--- Performing adb backup" + includeKeyValue + " ---");
TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>();
FullBackupObbConnection obbConnection = new FullBackupObbConnection();
@@ -4361,14 +4375,26 @@
// Now we cull any inapplicable / inappropriate packages from the set. This
// includes the special shared-storage agent package; we handle that one
- // explicitly at the end of the backup pass.
+ // explicitly at the end of the backup pass. Packages supporting key-value backup are
+ // added to their own queue, and handled after packages supporting fullbackup.
+ ArrayList<PackageInfo> keyValueBackupQueue = new ArrayList<>();
Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
while (iter.hasNext()) {
PackageInfo pkg = iter.next().getValue();
if (!appIsEligibleForBackup(pkg.applicationInfo)
- || appIsStopped(pkg.applicationInfo)
- || appIsKeyValueOnly(pkg)) {
+ || appIsStopped(pkg.applicationInfo)) {
iter.remove();
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkg.packageName
+ + " is not eligible for backup, removing.");
+ }
+ } else if (appIsKeyValueOnly(pkg)) {
+ iter.remove();
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkg.packageName
+ + " is key-value.");
+ }
+ keyValueBackupQueue.add(pkg);
}
}
@@ -4402,7 +4428,7 @@
// final '\n'.
//
// line 1: "ANDROID BACKUP"
- // line 2: backup file format version, currently "2"
+ // line 2: backup file format version, currently "5"
// line 3: compressed? "0" if not compressed, "1" if compressed.
// line 4: name of encryption algorithm [currently only "none" or "AES-256"]
//
@@ -4462,10 +4488,14 @@
}
}
- // Now actually run the constructed backup sequence
+ // Now actually run the constructed backup sequence for full backup
int N = backupQueue.size();
for (int i = 0; i < N; i++) {
pkg = backupQueue.get(i);
+ if (DEBUG) {
+ Slog.i(TAG,"--- Performing full backup for package " + pkg.packageName
+ + " ---");
+ }
final boolean isSharedStorage =
pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
@@ -4485,6 +4515,21 @@
}
}
}
+ // And for key-value backup if enabled
+ if (mKeyValue) {
+ for (PackageInfo keyValuePackage : keyValueBackupQueue) {
+ if (DEBUG) {
+ Slog.i(TAG, "--- Performing key-value backup for package "
+ + keyValuePackage.packageName + " ---");
+ }
+ KeyValueAdbBackupEngine kvBackupEngine =
+ new KeyValueAdbBackupEngine(out, keyValuePackage,
+ BackupManagerService.this,
+ mPackageManager, mBaseStateDir, mDataDir);
+ sendOnBackupPackage(keyValuePackage.packageName);
+ kvBackupEngine.backupOnePackage();
+ }
+ }
// Done!
finalizeBackup(out);
@@ -6693,19 +6738,24 @@
try {
// okay, presume we're okay, and extract the various metadata
info = new FileMetadata();
- info.size = extractRadix(block, 124, 12, 8);
- info.mtime = extractRadix(block, 136, 12, 8);
- info.mode = extractRadix(block, 100, 8, 8);
+ info.size = extractRadix(block, TAR_HEADER_OFFSET_FILESIZE,
+ TAR_HEADER_LENGTH_FILESIZE, TAR_HEADER_LONG_RADIX);
+ info.mtime = extractRadix(block, TAR_HEADER_OFFSET_MODTIME,
+ TAR_HEADER_LENGTH_MODTIME, TAR_HEADER_LONG_RADIX);
+ info.mode = extractRadix(block, TAR_HEADER_OFFSET_MODE,
+ TAR_HEADER_LENGTH_MODE, TAR_HEADER_LONG_RADIX);
- info.path = extractString(block, 345, 155); // prefix
- String path = extractString(block, 0, 100);
+ info.path = extractString(block, TAR_HEADER_OFFSET_PATH_PREFIX,
+ TAR_HEADER_LENGTH_PATH_PREFIX);
+ String path = extractString(block, TAR_HEADER_OFFSET_PATH,
+ TAR_HEADER_LENGTH_PATH);
if (path.length() > 0) {
if (info.path.length() > 0) info.path += '/';
info.path += path;
}
// tar link indicator field: 1 byte at offset 156 in the header.
- int typeChar = block[156];
+ int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
if (typeChar == 'x') {
// pax extended header, so we need to read that
gotHeader = readPaxExtendedHeader(instream, info);
@@ -6716,7 +6766,7 @@
}
if (!gotHeader) throw new IOException("Bad or missing pax header");
- typeChar = block[156];
+ typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
}
switch (typeChar) {
@@ -7037,6 +7087,7 @@
IFullBackupRestoreObserver mObserver;
AtomicBoolean mLatchObject;
IBackupAgent mAgent;
+ PackageManagerBackupAgent mPackageManagerBackupAgent;
String mAgentPackage;
ApplicationInfo mTargetApp;
FullBackupObbConnection mObbConnection = null;
@@ -7088,6 +7139,7 @@
mObserver = observer;
mLatchObject = latch;
mAgent = null;
+ mPackageManagerBackupAgent = new PackageManagerBackupAgent(mPackageManager);
mAgentPackage = null;
mTargetApp = null;
mObbConnection = new FullBackupObbConnection();
@@ -7505,14 +7557,21 @@
long toCopy = info.size;
final int token = generateToken();
try {
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null,
+ prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, null,
OP_TYPE_RESTORE_WAIT);
- if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
+ if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
mObbConnection.restoreObbFile(pkg, mPipes[0],
info.size, info.type, info.path, info.mode,
info.mtime, token, mBackupManagerBinder);
+ } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
+ if (DEBUG) Slog.d(TAG, "Restoring key-value file for " + pkg
+ + " : " + info.path);
+ KeyValueAdbRestoreEngine restoreEngine =
+ new KeyValueAdbRestoreEngine(BackupManagerService.this,
+ mDataDir, info, mPipes[0], mAgent, token);
+ new Thread(restoreEngine, "restore-key-value-runner").start();
} else {
if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ info.path);
@@ -8100,6 +8159,7 @@
Slog.i(TAG, b.toString());
}
}
+
// Consume a tar file header block [sequence] and accumulate the relevant metadata
FileMetadata readTarHeaders(InputStream instream) throws IOException {
byte[] block = new byte[512];
@@ -9920,16 +9980,16 @@
return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
}
- // Run a *full* backup pass for the given packages, writing the resulting data stream
+ // Run a backup pass for the given packages, writing the resulting data stream
// to the supplied file descriptor. This method is synchronous and does not return
// to the caller until the backup has been completed.
//
// This is the variant used by 'adb backup'; it requires on-screen confirmation
// by the user because it can be used to offload data over untrusted USB.
- public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
- boolean includeObbs, boolean includeShared, boolean doWidgets,
- boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) {
- mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
+ public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
+ boolean compress, boolean doKeyValue, String[] pkgList) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
final int callingUserHandle = UserHandle.getCallingUserId();
// TODO: http://b/22388012
@@ -9954,27 +10014,28 @@
try {
// Doesn't make sense to do a full backup prior to setup
if (!deviceIsProvisioned()) {
- Slog.i(TAG, "Full backup not supported before setup");
+ Slog.i(TAG, "Backup not supported before setup");
return;
}
- if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks
- + " obb=" + includeObbs + " shared=" + includeShared + " all=" + doAllApps
- + " system=" + includeSystem + " pkgs=" + pkgList);
- Slog.i(TAG, "Beginning full backup...");
+ if (DEBUG) Slog.v(TAG, "Requesting backup: apks=" + includeApks + " obb=" + includeObbs
+ + " shared=" + includeShared + " all=" + doAllApps + " system="
+ + includeSystem + " includekeyvalue=" + doKeyValue + " pkgs=" + pkgList);
+ Slog.i(TAG, "Beginning adb backup...");
- FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
- includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList);
+ AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
+ includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
+ pkgList);
final int token = generateToken();
- synchronized (mFullConfirmations) {
- mFullConfirmations.put(token, params);
+ synchronized (mAdbBackupRestoreConfirmations) {
+ mAdbBackupRestoreConfirmations.put(token, params);
}
// start up the confirmation UI
if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
- Slog.e(TAG, "Unable to launch full backup confirmation");
- mFullConfirmations.delete(token);
+ Slog.e(TAG, "Unable to launch backup confirmation UI");
+ mAdbBackupRestoreConfirmations.delete(token);
return;
}
@@ -9987,7 +10048,7 @@
startConfirmationTimeout(token, params);
// wait for the backup to be performed
- if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
+ if (DEBUG) Slog.d(TAG, "Waiting for backup completion...");
waitForCompletion(params);
} finally {
try {
@@ -9996,7 +10057,7 @@
// just eat it
}
Binder.restoreCallingIdentity(oldId);
- Slog.d(TAG, "Full backup processing complete.");
+ Slog.d(TAG, "Adb backup processing complete.");
}
}
@@ -10049,8 +10110,8 @@
}
}
- public void fullRestore(ParcelFileDescriptor fd) {
- mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore");
+ public void adbRestore(ParcelFileDescriptor fd) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
final int callingUserHandle = UserHandle.getCallingUserId();
// TODO: http://b/22388012
@@ -10068,19 +10129,19 @@
return;
}
- Slog.i(TAG, "Beginning full restore...");
+ Slog.i(TAG, "Beginning restore...");
- FullRestoreParams params = new FullRestoreParams(fd);
+ AdbRestoreParams params = new AdbRestoreParams(fd);
final int token = generateToken();
- synchronized (mFullConfirmations) {
- mFullConfirmations.put(token, params);
+ synchronized (mAdbBackupRestoreConfirmations) {
+ mAdbBackupRestoreConfirmations.put(token, params);
}
// start up the confirmation UI
if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
- Slog.e(TAG, "Unable to launch full restore confirmation");
- mFullConfirmations.delete(token);
+ Slog.e(TAG, "Unable to launch restore confirmation");
+ mAdbBackupRestoreConfirmations.delete(token);
return;
}
@@ -10093,16 +10154,16 @@
startConfirmationTimeout(token, params);
// wait for the restore to be performed
- if (DEBUG) Slog.d(TAG, "Waiting for full restore completion...");
+ if (DEBUG) Slog.d(TAG, "Waiting for restore completion...");
waitForCompletion(params);
} finally {
try {
fd.close();
} catch (IOException e) {
- Slog.w(TAG, "Error trying to close fd after full restore: " + e);
+ Slog.w(TAG, "Error trying to close fd after adb restore: " + e);
}
Binder.restoreCallingIdentity(oldId);
- Slog.i(TAG, "Full restore processing complete.");
+ Slog.i(TAG, "adb restore processing complete.");
}
}
@@ -10120,7 +10181,7 @@
return true;
}
- void startConfirmationTimeout(int token, FullParams params) {
+ void startConfirmationTimeout(int token, AdbParams params) {
if (MORE_DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
+ TIMEOUT_FULL_CONFIRMATION + " millis");
Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
@@ -10128,7 +10189,7 @@
mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
}
- void waitForCompletion(FullParams params) {
+ void waitForCompletion(AdbParams params) {
synchronized (params.latch) {
while (params.latch.get() == false) {
try {
@@ -10138,7 +10199,7 @@
}
}
- void signalFullBackupRestoreCompletion(FullParams params) {
+ void signalAdbBackupRestoreCompletion(AdbParams params) {
synchronized (params.latch) {
params.latch.set(true);
params.latch.notifyAll();
@@ -10147,27 +10208,27 @@
// Confirm that the previously-requested full backup/restore operation can proceed. This
// is used to require a user-facing disclosure about the operation.
- public void acknowledgeFullBackupOrRestore(int token, boolean allow,
+ public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
- if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token
+ if (DEBUG) Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token
+ " allow=" + allow);
// TODO: possibly require not just this signature-only permission, but even
// require that the specific designated confirmation-UI app uid is the caller?
- mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeFullBackupOrRestore");
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeAdbBackupOrRestore");
long oldId = Binder.clearCallingIdentity();
try {
- FullParams params;
- synchronized (mFullConfirmations) {
- params = mFullConfirmations.get(token);
+ AdbParams params;
+ synchronized (mAdbBackupRestoreConfirmations) {
+ params = mAdbBackupRestoreConfirmations.get(token);
if (params != null) {
mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params);
- mFullConfirmations.delete(token);
+ mAdbBackupRestoreConfirmations.delete(token);
if (allow) {
- final int verb = params instanceof FullBackupParams
+ final int verb = params instanceof AdbBackupParams
? MSG_RUN_ADB_BACKUP
: MSG_RUN_ADB_RESTORE;
@@ -10183,7 +10244,7 @@
} else {
Slog.w(TAG, "User rejected full backup/restore operation");
// indicate completion without having actually transferred any data
- signalFullBackupRestoreCompletion(params);
+ signalAdbBackupRestoreCompletion(params);
}
} else {
Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
new file mode 100644
index 0000000..cd13760
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -0,0 +1,281 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.FullBackup;
+import android.app.backup.FullBackupDataOutput;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbBackupEngine {
+ private static final String TAG = "KeyValueAdbBackupEngine";
+ private static final boolean DEBUG = false;
+
+ private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir";
+ private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state";
+ private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
+ private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";
+
+ private BackupManagerService mBackupManagerService;
+ private final PackageManager mPackageManager;
+ private final OutputStream mOutput;
+ private final PackageInfo mCurrentPackage;
+ private final File mDataDir;
+ private final File mStateDir;
+ private final File mBlankStateName;
+ private final File mBackupDataName;
+ private final File mNewStateName;
+ private final File mManifestFile;
+ private ParcelFileDescriptor mSavedState;
+ private ParcelFileDescriptor mBackupData;
+ private ParcelFileDescriptor mNewState;
+
+ KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
+ BackupManagerService backupManagerService, PackageManager packageManager,
+ File baseStateDir, File dataDir) {
+ mOutput = output;
+ mCurrentPackage = packageInfo;
+ mBackupManagerService = backupManagerService;
+ mPackageManager = packageManager;
+
+ mDataDir = dataDir;
+ mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME);
+ mStateDir.mkdirs();
+
+ String pkg = mCurrentPackage.packageName;
+
+ mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME);
+ mBackupDataName = new File(mDataDir,
+ pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX);
+ mNewStateName = new File(mStateDir,
+ pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
+
+ mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+ }
+
+ void backupOnePackage() throws IOException {
+ ApplicationInfo targetApp = mCurrentPackage.applicationInfo;
+
+ try {
+ prepareBackupFiles(mCurrentPackage.packageName);
+
+ IBackupAgent agent = bindToAgent(targetApp);
+
+ if (agent == null) {
+ // We failed binding to the agent, so ignore this package
+ Slog.e(TAG, "Failed binding to BackupAgent for package "
+ + mCurrentPackage.packageName);
+ return;
+ }
+
+ // We are bound to agent, initiate backup.
+ if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) {
+ // Backup failed, skip package.
+ Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName);
+ return;
+ }
+
+ // Backup finished successfully. Copy the backup data to the output stream.
+ writeBackupData();
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName
+ + " will ignore package. " + e);
+ } finally {
+ // We are either done, failed or have timed out, so do cleanup and kill the agent.
+ cleanup();
+ }
+ }
+
+ private void prepareBackupFiles(String packageName) throws FileNotFoundException {
+
+ // We pass a blank state to make sure we are getting the complete backup, not just an
+ // increment
+ mSavedState = ParcelFileDescriptor.open(mBlankStateName,
+ MODE_READ_ONLY | MODE_CREATE); // Make an empty file if necessary
+
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+ if (!SELinux.restorecon(mBackupDataName)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
+ }
+
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
+ MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+ }
+
+ private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
+ try {
+ return mBackupManagerService.bindToAgentSynchronous(targetApp,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
+ + ". " + e);
+ return null;
+ }
+ }
+
+ // Return true on backup success, false otherwise
+ private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
+ int token = mBackupManagerService.generateToken();
+ try {
+ mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+ OP_TYPE_BACKUP_WAIT);
+
+ // Start backup and wait for BackupManagerService to get callback for success or timeout
+ agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
+ mBackupManagerService.mBackupManagerBinder);
+ if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+ Slog.e(TAG, "Key-value backup failed on package " + packageName);
+ return false;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "Key-value backup success for package " + packageName);
+ }
+ return true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e);
+ return false;
+ }
+ }
+
+ class KeyValueAdbBackupDataCopier implements Runnable {
+ private final PackageInfo mPackage;
+ private final ParcelFileDescriptor mPipe;
+ private final int mToken;
+
+ KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe,
+ int token)
+ throws IOException {
+ mPackage = pack;
+ mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
+ mToken = token;
+ }
+
+ @Override
+ public void run() {
+ try {
+ FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
+ }
+ BackupManagerService.writeAppManifest(
+ mPackage, mPackageManager, mManifestFile, false, false);
+ FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+ mDataDir.getAbsolutePath(),
+ mManifestFile.getAbsolutePath(),
+ output);
+ mManifestFile.delete();
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName);
+ }
+ FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+ mDataDir.getAbsolutePath(),
+ mBackupDataName.getAbsolutePath(),
+ output);
+
+ // Write EOD marker
+ try {
+ FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor());
+ byte[] buf = new byte[4];
+ out.write(buf);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to finalize backup stream!");
+ }
+
+ try {
+ mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0);
+ } catch (RemoteException e) {
+ // we'll time out anyway, so we're safe
+ }
+
+ } catch (IOException e) {
+ Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e);
+ } finally {
+ IoUtils.closeQuietly(mPipe);
+ }
+ }
+ }
+
+ private void writeBackupData() throws IOException {
+
+ int token = mBackupManagerService.generateToken();
+
+ ParcelFileDescriptor[] pipes = null;
+ try {
+ pipes = ParcelFileDescriptor.createPipe();
+
+ mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+ OP_TYPE_BACKUP_WAIT);
+
+ // We will have to create a runnable that will read the manifest and backup data we
+ // created, such that we can pipe the data into mOutput. The reason we do this is that
+ // internally FullBackup.backupToTar is used, which will create the necessary file
+ // header, but will also chunk the data. The method routeSocketDataToOutput in
+ // BackupManagerService will dechunk the data, and append it to the TAR outputstream.
+ KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1],
+ token);
+ pipes[1].close(); // the runner has dup'd it
+ pipes[1] = null;
+ Thread t = new Thread(runner, "key-value-app-data-runner");
+ t.start();
+
+ // Now pull data from the app and stuff it into the output
+ BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+
+ if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+ Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName);
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e);
+ } finally {
+ // flush after every package
+ mOutput.flush();
+ if (pipes != null) {
+ IoUtils.closeQuietly(pipes[0]);
+ IoUtils.closeQuietly(pipes[1]);
+ }
+ }
+ }
+
+ private void cleanup() {
+ mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
+ mBlankStateName.delete();
+ mNewStateName.delete();
+ mBackupDataName.delete();
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
new file mode 100644
index 0000000..6fb9355
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -0,0 +1,148 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+
+import android.app.IBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackup;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.backup.BackupManagerService.FileMetadata;
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used by BackupManagerService to perform adb restore for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbRestoreEngine implements Runnable {
+ private static final String TAG = "KeyValueAdbRestoreEngine";
+ private static final boolean DEBUG = false;
+
+ private final BackupManagerService mBackupManagerService;
+ private final File mDataDir;
+
+ FileMetadata mInfo;
+ BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+ ParcelFileDescriptor mInFD;
+ IBackupAgent mAgent;
+ int mToken;
+
+ KeyValueAdbRestoreEngine(BackupManagerService backupManagerService, File dataDir,
+ FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent, int token) {
+ mBackupManagerService = backupManagerService;
+ mDataDir = dataDir;
+ mInfo = info;
+ mInFD = inFD;
+ mAgent = agent;
+ mToken = token;
+ }
+
+ @Override
+ public void run() {
+ try {
+ File restoreData = prepareRestoreData(mInfo, mInFD);
+
+ // TODO: version ?
+ invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private File prepareRestoreData(FileMetadata info, ParcelFileDescriptor inFD) throws IOException {
+ String pkg = info.packageName;
+ File restoreDataName = new File(mDataDir, pkg + ".restore");
+ File sortedDataName = new File(mDataDir, pkg + ".sorted");
+
+ FullBackup.restoreFile(inFD, info.size, info.type, info.mode, info.mtime, restoreDataName);
+
+ // Sort the keys, as the BackupAgent expect them to come in lexicographical order
+ sortKeyValueData(restoreDataName, sortedDataName);
+ return sortedDataName;
+ }
+
+ private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData,
+ int versionCode) throws IOException {
+ String pkg = info.packageName;
+ File newStateName = new File(mDataDir, pkg + ".new");
+ try {
+ ParcelFileDescriptor backupData =
+ ParcelFileDescriptor.open(restoreData, MODE_READ_ONLY);
+ ParcelFileDescriptor newState = ParcelFileDescriptor.open(newStateName,
+ MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Starting restore of package " + pkg + " for version code "
+ + versionCode);
+ }
+ agent.doRestore(backupData, versionCode, newState, mToken,
+ mBackupManagerService.mBackupManagerBinder);
+ } catch (IOException e) {
+ Slog.e(TAG, "Exception opening file. " + e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception calling doRestore on agent: " + e);
+ }
+ }
+
+ private void sortKeyValueData (File restoreData, File sortedData) throws IOException {
+ FileInputStream inputStream = null;
+ FileOutputStream outputStream = null;
+ try {
+ inputStream = new FileInputStream(restoreData);
+ outputStream = new FileOutputStream(sortedData);
+ BackupDataInput reader = new BackupDataInput(inputStream.getFD());
+ BackupDataOutput writer = new BackupDataOutput(outputStream.getFD());
+ copyKeysInLexicalOrder(reader, writer);
+ } finally {
+ if (inputStream != null) {
+ IoUtils.closeQuietly(inputStream);
+ }
+ if (outputStream != null) {
+ IoUtils.closeQuietly(outputStream);
+ }
+ }
+ }
+
+ private void copyKeysInLexicalOrder(BackupDataInput in, BackupDataOutput out)
+ throws IOException {
+ Map<String, byte[]> data = new HashMap<>();
+ while (in.readNextHeader()) {
+ String key = in.getKey();
+ int size = in.getDataSize();
+ if (size < 0) {
+ in.skipEntityData();
+ continue;
+ }
+ byte[] value = new byte[size];
+ in.readEntityData(value, 0, size);
+ data.put(key, value);
+ }
+ List<String> keys = new ArrayList<>(data.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ byte[] value = data.get(key);
+ out.writeEntityHeader(key, value.length);
+ out.writeEntityData(value, value.length);
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 8855661..c40f2ca 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -227,14 +227,14 @@
}
@Override
- public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean allApps,
- boolean allIncludesSystem, boolean doCompress, String[] packageNames)
+ boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.fullBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
- allApps, allIncludesSystem, doCompress, packageNames);
+ svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
+ allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
}
}
@@ -247,10 +247,10 @@
}
@Override
- public void fullRestore(ParcelFileDescriptor fd) throws RemoteException {
+ public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.fullRestore(fd);
+ svc.adbRestore(fd);
}
}
@@ -260,7 +260,7 @@
throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.acknowledgeFullBackupOrRestore(token, allow,
+ svc.acknowledgeAdbBackupOrRestore(token, allow,
curPassword, encryptionPassword, observer);
}
}
diff --git a/services/core/Android.mk b/services/core/Android.mk
index e35a171..099f557 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -22,7 +22,8 @@
services.net \
android.hardware.light@2.0-java \
android.hardware.power@1.0-java \
- android.hardware.tv.cec@1.0-java
+ android.hardware.tv.cec@1.0-java \
+ android.hidl.manager@1.0-java
LOCAL_STATIC_JAVA_LIBRARIES := \
tzdata_shared2 \
diff --git a/services/core/java/com/android/server/NetworkManagementInternal.java b/services/core/java/com/android/server/NetworkManagementInternal.java
new file mode 100644
index 0000000..f53c454
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkManagementInternal.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+/**
+ * NetworkManagement local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class NetworkManagementInternal {
+ /**
+ * Checks if network is restricted for {@param uid} as per the app idle state, device idle mode,
+ * battery and data saver modes.
+ */
+ public abstract boolean isNetworkRestrictedForUid(int uid);
+}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index adc5e33..74328c0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -27,7 +27,9 @@
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.FIREWALL_TYPE_BLACKLIST;
import static android.net.NetworkPolicyManager.FIREWALL_TYPE_WHITELIST;
import static android.net.NetworkStats.SET_DEFAULT;
@@ -90,6 +92,7 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.HexDump;
@@ -222,7 +225,12 @@
private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
+ /**
+ * If both locks need to be held, then they should be obtained in the order:
+ * first {@link #mQuotaLock} and then {@link #mRulesLock}.
+ */
private Object mQuotaLock = new Object();
+ private Object mRulesLock = new Object();
/** Set of interfaces with active quotas. */
@GuardedBy("mQuotaLock")
@@ -231,41 +239,41 @@
@GuardedBy("mQuotaLock")
private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
/** Set of UIDs blacklisted on metered networks. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
/** Set of UIDs whitelisted on metered networks. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
/** Set of UIDs with cleartext penalties. */
@GuardedBy("mQuotaLock")
private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
/** Set of UIDs that are to be blocked/allowed by firewall controller. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to application idles.
*/
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to device idles.
*/
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to device on power-save mode.
*/
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
/** Set of states for the child firewall chains. True if the chain is active. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@GuardedBy("mQuotaLock")
- private boolean mDataSaverMode;
+ private volatile boolean mDataSaverMode;
private Object mIdleTimerLock = new Object();
/** Set of interfaces with active idle timers. */
@@ -321,6 +329,17 @@
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
+
+ LocalServices.addService(NetworkManagementInternal.class, new LocalService());
+ }
+
+ @VisibleForTesting
+ NetworkManagementService() {
+ mConnector = null;
+ mContext = null;
+ mDaemonHandler = null;
+ mFgHandler = null;
+ mThread = null;
}
static NetworkManagementService create(Context context, String socket)
@@ -502,21 +521,24 @@
}
// Sync the state of the given chain with the native daemon.
- private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) {
- int size = uidFirewallRules.size();
- if (size > 0) {
+ private void syncFirewallChainLocked(int chain, String name) {
+ SparseIntArray rules;
+ synchronized (mRulesLock) {
+ final SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
// Make a copy of the current rules, and then clear them. This is because
- // setFirewallUidRuleInternal only pushes down rules to the native daemon if they are
- // different from the current rules stored in the mUidFirewall*Rules array for the
- // specified chain. If we don't clear the rules, setFirewallUidRuleInternal will do
- // nothing.
- final SparseIntArray rules = uidFirewallRules.clone();
+ // setFirewallUidRuleInternal only pushes down rules to the native daemon if they
+ // are different from the current rules stored in the mUidFirewall*Rules array for
+ // the specified chain. If we don't clear the rules, setFirewallUidRuleInternal
+ // will do nothing.
+ rules = uidFirewallRules.clone();
uidFirewallRules.clear();
-
+ }
+ if (rules.size() > 0) {
// Now push the rules. setFirewallUidRuleInternal will push each of these down to the
// native daemon, and also add them to the mUidFirewall*Rules array for the specified
// chain.
- if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall " + name + "UID rules");
+ if (DBG) Slog.d(TAG, "Pushing " + rules.size() + " active firewall "
+ + name + "UID rules");
for (int i = 0; i < rules.size(); i++) {
setFirewallUidRuleLocked(chain, rules.keyAt(i), rules.valueAt(i));
}
@@ -597,22 +619,30 @@
}
}
- size = mUidRejectOnMetered.size();
- if (size > 0) {
- if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
- final SparseBooleanArray uidRejectOnQuota = mUidRejectOnMetered;
- mUidRejectOnMetered = new SparseBooleanArray();
+ SparseBooleanArray uidRejectOnQuota = null;
+ SparseBooleanArray uidAcceptOnQuota = null;
+ synchronized (mRulesLock) {
+ size = mUidRejectOnMetered.size();
+ if (size > 0) {
+ if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
+ uidRejectOnQuota = mUidRejectOnMetered;
+ mUidRejectOnMetered = new SparseBooleanArray();
+ }
+
+ size = mUidAllowOnMetered.size();
+ if (size > 0) {
+ if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
+ uidAcceptOnQuota = mUidAllowOnMetered;
+ mUidAllowOnMetered = new SparseBooleanArray();
+ }
+ }
+ if (uidRejectOnQuota != null) {
for (int i = 0; i < uidRejectOnQuota.size(); i++) {
setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i),
uidRejectOnQuota.valueAt(i));
}
}
-
- size = mUidAllowOnMetered.size();
- if (size > 0) {
- if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
- final SparseBooleanArray uidAcceptOnQuota = mUidAllowOnMetered;
- mUidAllowOnMetered = new SparseBooleanArray();
+ if (uidAcceptOnQuota != null) {
for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i),
uidAcceptOnQuota.valueAt(i));
@@ -631,20 +661,17 @@
setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
- syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
- syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
- syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mUidFirewallDozableRules, "dozable ");
- syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, mUidFirewallPowerSaveRules,
- "powersave ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_NONE, "");
+ syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
- if (mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)) {
- setFirewallChainEnabled(FIREWALL_CHAIN_STANDBY, true);
- }
- if (mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)) {
- setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true);
- }
- if (mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE)) {
- setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE, true);
+ final int[] chains =
+ {FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE};
+ for (int chain : chains) {
+ if (getFirewallChainState(chain)) {
+ setFirewallChainEnabled(chain, true);
+ }
}
}
}
@@ -1602,8 +1629,7 @@
}
}
- private void setUidOnMeteredNetworkList(SparseBooleanArray quotaList, int uid,
- boolean blacklist, boolean enable) {
+ private void setUidOnMeteredNetworkList(int uid, boolean blacklist, boolean enable) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
// silently discard when control disabled
@@ -1614,7 +1640,12 @@
final String suffix = enable ? "add" : "remove";
synchronized (mQuotaLock) {
- final boolean oldEnable = quotaList.get(uid, false);
+ boolean oldEnable;
+ SparseBooleanArray quotaList;
+ synchronized (mRulesLock) {
+ quotaList = blacklist ? mUidRejectOnMetered : mUidAllowOnMetered;
+ oldEnable = quotaList.get(uid, false);
+ }
if (oldEnable == enable) {
// TODO: eventually consider throwing
return;
@@ -1623,10 +1654,12 @@
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
try {
mConnector.execute("bandwidth", suffix + chain, uid);
- if (enable) {
- quotaList.put(uid, true);
- } else {
- quotaList.delete(uid);
+ synchronized (mRulesLock) {
+ if (enable) {
+ quotaList.put(uid, true);
+ } else {
+ quotaList.delete(uid);
+ }
}
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
@@ -1638,12 +1671,12 @@
@Override
public void setUidMeteredNetworkBlacklist(int uid, boolean enable) {
- setUidOnMeteredNetworkList(mUidRejectOnMetered, uid, true, enable);
+ setUidOnMeteredNetworkList(uid, true, enable);
}
@Override
public void setUidMeteredNetworkWhitelist(int uid, boolean enable) {
- setUidOnMeteredNetworkList(mUidAllowOnMetered, uid, false, enable);
+ setUidOnMeteredNetworkList(uid, false, enable);
}
@Override
@@ -1934,7 +1967,6 @@
// UID ranges whose sockets we won't touch.
int[] exemptUids;
- final SparseIntArray rules = getUidFirewallRules(chain);
int numUids = 0;
if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) {
@@ -1945,11 +1977,14 @@
new UidRange(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE),
};
// ... except for the UIDs that have allow rules.
- exemptUids = new int[rules.size()];
- for (int i = 0; i < exemptUids.length; i++) {
- if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
- exemptUids[numUids] = rules.keyAt(i);
- numUids++;
+ synchronized (mRulesLock) {
+ final SparseIntArray rules = getUidFirewallRulesLR(chain);
+ exemptUids = new int[rules.size()];
+ for (int i = 0; i < exemptUids.length; i++) {
+ if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
+ exemptUids[numUids] = rules.keyAt(i);
+ numUids++;
+ }
}
}
// Normally, whitelist chains only contain deny rules, so numUids == exemptUids.length.
@@ -1964,12 +1999,15 @@
}
} else {
// Close sockets for every UID that has a deny rule...
- ranges = new UidRange[rules.size()];
- for (int i = 0; i < ranges.length; i++) {
- if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) {
- int uid = rules.keyAt(i);
- ranges[numUids] = new UidRange(uid, uid);
- numUids++;
+ synchronized (mRulesLock) {
+ final SparseIntArray rules = getUidFirewallRulesLR(chain);
+ ranges = new UidRange[rules.size()];
+ for (int i = 0; i < ranges.length; i++) {
+ if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) {
+ int uid = rules.keyAt(i);
+ ranges[numUids] = new UidRange(uid, uid);
+ numUids++;
+ }
}
}
// As above; usually numUids == ranges.length, but not always.
@@ -1991,12 +2029,14 @@
public void setFirewallChainEnabled(int chain, boolean enable) {
enforceSystemUid();
synchronized (mQuotaLock) {
- if (mFirewallChainStates.get(chain) == enable) {
- // All is the same, nothing to do. This relies on the fact that netd has child
- // chains default detached.
- return;
+ synchronized (mRulesLock) {
+ if (getFirewallChainState(chain) == enable) {
+ // All is the same, nothing to do. This relies on the fact that netd has child
+ // chains default detached.
+ return;
+ }
+ setFirewallChainState(chain, enable);
}
- mFirewallChainStates.put(chain, enable);
final String operation = enable ? "enable_chain" : "disable_chain";
final String chainName;
@@ -2048,27 +2088,29 @@
public void setFirewallUidRules(int chain, int[] uids, int[] rules) {
enforceSystemUid();
synchronized (mQuotaLock) {
- SparseIntArray uidFirewallRules = getUidFirewallRules(chain);
- SparseIntArray newRules = new SparseIntArray();
- // apply new set of rules
- for (int index = uids.length - 1; index >= 0; --index) {
- int uid = uids[index];
- int rule = rules[index];
- updateFirewallUidRuleLocked(chain, uid, rule);
- newRules.put(uid, rule);
- }
- // collect the rules to remove.
- SparseIntArray rulesToRemove = new SparseIntArray();
- for (int index = uidFirewallRules.size() - 1; index >= 0; --index) {
- int uid = uidFirewallRules.keyAt(index);
- if (newRules.indexOfKey(uid) < 0) {
- rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT);
+ synchronized (mRulesLock) {
+ SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
+ SparseIntArray newRules = new SparseIntArray();
+ // apply new set of rules
+ for (int index = uids.length - 1; index >= 0; --index) {
+ int uid = uids[index];
+ int rule = rules[index];
+ updateFirewallUidRuleLocked(chain, uid, rule);
+ newRules.put(uid, rule);
}
- }
- // remove dead rules
- for (int index = rulesToRemove.size() - 1; index >= 0; --index) {
- int uid = rulesToRemove.keyAt(index);
- updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
+ // collect the rules to remove.
+ SparseIntArray rulesToRemove = new SparseIntArray();
+ for (int index = uidFirewallRules.size() - 1; index >= 0; --index) {
+ int uid = uidFirewallRules.keyAt(index);
+ if (newRules.indexOfKey(uid) < 0) {
+ rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT);
+ }
+ }
+ // remove dead rules
+ for (int index = rulesToRemove.size() - 1; index >= 0; --index) {
+ int uid = rulesToRemove.keyAt(index);
+ updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
+ }
}
try {
switch (chain) {
@@ -2112,28 +2154,30 @@
// TODO: now that netd supports batching, NMS should not keep these data structures anymore...
private boolean updateFirewallUidRuleLocked(int chain, int uid, int rule) {
- SparseIntArray uidFirewallRules = getUidFirewallRules(chain);
+ synchronized (mRulesLock) {
+ SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
- final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
- if (DBG) {
- Slog.d(TAG, "oldRule = " + oldUidFirewallRule
- + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain);
- }
- if (oldUidFirewallRule == rule) {
- if (DBG) Slog.d(TAG, "!!!!! Skipping change");
- // TODO: eventually consider throwing
- return false;
- }
+ final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
+ if (DBG) {
+ Slog.d(TAG, "oldRule = " + oldUidFirewallRule
+ + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain);
+ }
+ if (oldUidFirewallRule == rule) {
+ if (DBG) Slog.d(TAG, "!!!!! Skipping change");
+ // TODO: eventually consider throwing
+ return false;
+ }
- String ruleName = getFirewallRuleName(chain, rule);
- String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
+ String ruleName = getFirewallRuleName(chain, rule);
+ String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
- if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
- uidFirewallRules.delete(uid);
- } else {
- uidFirewallRules.put(uid, rule);
+ if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
+ uidFirewallRules.delete(uid);
+ } else {
+ uidFirewallRules.put(uid, rule);
+ }
+ return !ruleName.equals(oldRuleName);
}
- return !ruleName.equals(oldRuleName);
}
private @NonNull String getFirewallRuleName(int chain, int rule) {
@@ -2154,7 +2198,7 @@
return ruleName;
}
- private @NonNull SparseIntArray getUidFirewallRules(int chain) {
+ private @NonNull SparseIntArray getUidFirewallRulesLR(int chain) {
switch (chain) {
case FIREWALL_CHAIN_STANDBY:
return mUidFirewallStandbyRules;
@@ -2284,29 +2328,25 @@
pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
pw.print("Data saver mode: "); pw.println(mDataSaverMode);
- dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
- dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+ synchronized (mRulesLock) {
+ dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
+ dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+ }
}
- synchronized (mUidFirewallRules) {
+ synchronized (mRulesLock) {
dumpUidFirewallRule(pw, "", mUidFirewallRules);
- }
- pw.print("UID firewall standby chain enabled: "); pw.println(
- mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY));
- synchronized (mUidFirewallStandbyRules) {
+ pw.print("UID firewall standby chain enabled: "); pw.println(
+ getFirewallChainState(FIREWALL_CHAIN_STANDBY));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_STANDBY, mUidFirewallStandbyRules);
- }
- pw.print("UID firewall dozable chain enabled: "); pw.println(
- mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE));
- synchronized (mUidFirewallDozableRules) {
+ pw.print("UID firewall dozable chain enabled: "); pw.println(
+ getFirewallChainState(FIREWALL_CHAIN_DOZABLE));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_DOZABLE, mUidFirewallDozableRules);
- }
- pw.println("UID firewall powersave chain enabled: " +
- mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE));
- synchronized (mUidFirewallPowerSaveRules) {
+ pw.println("UID firewall powersave chain enabled: " +
+ getFirewallChainState(FIREWALL_CHAIN_POWERSAVE));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_POWERSAVE, mUidFirewallPowerSaveRules);
}
@@ -2576,4 +2616,99 @@
return failures;
}
+
+ private void setFirewallChainState(int chain, boolean state) {
+ synchronized (mRulesLock) {
+ mFirewallChainStates.put(chain, state);
+ }
+ }
+
+ private boolean getFirewallChainState(int chain) {
+ synchronized (mRulesLock) {
+ return mFirewallChainStates.get(chain);
+ }
+ }
+
+ @VisibleForTesting
+ class LocalService extends NetworkManagementInternal {
+ @Override
+ public boolean isNetworkRestrictedForUid(int uid) {
+ synchronized (mRulesLock) {
+ if (getFirewallChainState(FIREWALL_CHAIN_STANDBY)
+ && mUidFirewallStandbyRules.get(uid) == FIREWALL_RULE_DENY) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of app standby mode");
+ return true;
+ }
+ if (getFirewallChainState(FIREWALL_CHAIN_DOZABLE)
+ && mUidFirewallDozableRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of device idle mode");
+ return true;
+ }
+ if (getFirewallChainState(FIREWALL_CHAIN_POWERSAVE)
+ && mUidFirewallPowerSaveRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of power saver mode");
+ return true;
+ }
+ if (mUidRejectOnMetered.get(uid)) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
+ + " in the background");
+ return true;
+ }
+ if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ Injector getInjector() {
+ return new Injector();
+ }
+
+ @VisibleForTesting
+ class Injector {
+ void setDataSaverMode(boolean dataSaverMode) {
+ mDataSaverMode = dataSaverMode;
+ }
+
+ void setFirewallChainState(int chain, boolean state) {
+ NetworkManagementService.this.setFirewallChainState(chain, state);
+ }
+
+ void setFirewallRule(int chain, int uid, int rule) {
+ synchronized (mRulesLock) {
+ getUidFirewallRulesLR(chain).put(uid, rule);
+ }
+ }
+
+ void setUidOnMeteredNetworkList(boolean blacklist, int uid, boolean enable) {
+ synchronized (mRulesLock) {
+ if (blacklist) {
+ mUidRejectOnMetered.put(uid, enable);
+ } else {
+ mUidAllowOnMetered.put(uid, enable);
+ }
+ }
+ }
+
+ void reset() {
+ synchronized (mRulesLock) {
+ setDataSaverMode(false);
+ final int[] chains = {
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE
+ };
+ for (int chain : chains) {
+ setFirewallChainState(chain, false);
+ getUidFirewallRulesLR(chain).clear();
+ }
+ mUidAllowOnMetered.clear();
+ mUidRejectOnMetered.clear();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 891a13b..457c5f8 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -715,7 +715,8 @@
final Intent intent = new Intent(action,
Uri.fromFile(userVol.getPathFile()));
intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mContext.sendBroadcastAsUser(intent, userVol.getOwner());
}
break;
@@ -2079,6 +2080,20 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ if ((mask & StorageManager.DEBUG_VIRTUAL_DISK) != 0) {
+ final boolean enabled = (flags & StorageManager.DEBUG_VIRTUAL_DISK) != 0;
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SystemProperties.set(StorageManager.PROP_VIRTUAL_DISK, Boolean.toString(enabled));
+
+ // Reset storage to kick new setting into place
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 5115fde..e4f4687 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -323,52 +323,6 @@
}
@Override
- public void setTheme(String theme) {
- if (getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_THEME_OVERLAY)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
- return;
- }
- SystemProperties.set("persist.vendor.overlay.theme", theme);
- mHandler.post(() -> ShutdownThread.reboot(getContext(),
- PowerManager.SHUTDOWN_USER_REQUESTED, false));
- }
-
- @Override
- public String getTheme() {
- if (getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_THEME_OVERLAY)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
- return null;
- }
- return SystemProperties.get("persist.vendor.overlay.theme");
- }
-
- @Override
- public String[] getAvailableThemes() {
- if (getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_THEME_OVERLAY)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.e(TAG, "getAvailableThemes requires MODIFY_THEME_OVERLAY permission");
- return null;
- }
- String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
- if (TextUtils.isEmpty(def)) {
- def = null;
- }
- String[] fileList = new File("/vendor/overlay").list();
- if (fileList == null) return new String[0];
- ArrayList<String> options = new ArrayList(fileList.length + 1);
- Collections.addAll(options, fileList);
- if (!options.contains(def)) {
- options.add(0, def);
- }
- return options.toArray(new String[options.size()]);
- }
-
- @Override
public int getNightMode() {
synchronized (mLock) {
return mNightMode;
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ce4ca02..80f89fc 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hidl.manager.V1_0.IServiceManager;
import android.os.Debug;
import android.os.Handler;
import android.os.IPowerManager;
@@ -42,6 +43,9 @@
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
/** This class calls its monitor every minute. Killing this process if they don't return **/
public class Watchdog extends Thread {
@@ -75,6 +79,14 @@
"com.android.bluetooth", // Bluetooth service
};
+ public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
+ "android.hardware.audio@2.0::IDevicesFactory",
+ "android.hardware.bluetooth@1.0::IBluetoothHci",
+ "android.hardware.camera.provider@2.4::ICameraProvider",
+ "android.hardware.vr@1.0::IVr",
+ "android.hardware.media.omx@1.0::IOmx"
+ );
+
static Watchdog sWatchdog;
/* This handler will be used to post message back onto the main thread */
@@ -344,6 +356,43 @@
return builder.toString();
}
+ private ArrayList<Integer> getInterestingHalPids() {
+ try {
+ IServiceManager serviceManager = IServiceManager.getService();
+ ArrayList<IServiceManager.InstanceDebugInfo> dump =
+ serviceManager.debugDump();
+ HashSet<Integer> pids = new HashSet<>();
+ for (IServiceManager.InstanceDebugInfo info : dump) {
+ if (info.pid == IServiceManager.PidConstant.NO_PID) {
+ continue;
+ }
+
+ if (!HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
+ continue;
+ }
+
+ pids.add(info.pid);
+ }
+ return new ArrayList<Integer>(pids);
+ } catch (RemoteException e) {
+ return new ArrayList<Integer>();
+ }
+ }
+
+ private ArrayList<Integer> getInterestingNativePids() {
+ ArrayList<Integer> pids = getInterestingHalPids();
+
+ int[] nativePids = Process.getPidsForCommands(NATIVE_STACKS_OF_INTEREST);
+ if (nativePids != null) {
+ pids.ensureCapacity(pids.size() + nativePids.length);
+ for (int i : nativePids) {
+ pids.add(i);
+ }
+ }
+
+ return pids;
+ }
+
@Override
public void run() {
boolean waitedHalf = false;
@@ -400,7 +449,7 @@
ArrayList<Integer> pids = new ArrayList<Integer>();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(true, pids, null, null,
- NATIVE_STACKS_OF_INTEREST);
+ getInterestingNativePids());
waitedHalf = true;
}
continue;
@@ -417,13 +466,13 @@
// Then kill this process so that the system will restart.
EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
- ArrayList<Integer> pids = new ArrayList<Integer>();
+ ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
if (mPhonePid > 0) pids.add(mPhonePid);
// Pass !waitedHalf so that just in case we somehow wind up here without having
// dumped the halfway stacks, we properly re-initialize the trace file.
final File stack = ActivityManagerService.dumpStackTraces(
- !waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);
+ !waitedHalf, pids, null, null, getInterestingNativePids());
// Give some extra time to make sure the stack traces get written.
// The system's been hanging for a minute, another second or two won't hurt much.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4fc60f9..272fbf8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -54,6 +54,7 @@
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
+import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.provider.Settings.System.FONT_SCALE;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
@@ -357,6 +358,7 @@
import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.server.NetworkManagementInternal;
import com.android.server.RescueParty;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
@@ -572,9 +574,9 @@
static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
/**
- * Indicates the maximum time spent waiting for the network rules to get updated.
+ * Default value for {@link Settings.Global#NETWORK_ACCESS_TIMEOUT_MS}.
*/
- private static final long WAIT_FOR_NETWORK_TIMEOUT_MS = 2000; // 2 sec
+ private static final long NETWORK_ACCESS_TIMEOUT_DEFAULT_MS = 0; // 0 sec
/**
* State indicating that there is no need for any blocking for network.
@@ -753,6 +755,12 @@
final AppErrors mAppErrors;
+ /**
+ * Indicates the maximum time spent waiting for the network rules to get updated.
+ */
+ @VisibleForTesting
+ long mWaitForNetworkTimeoutMs;
+
public boolean canShowErrorDialogs() {
return mShowDialogs && !mSleeping && !mShuttingDown
&& !mKeyguardController.isKeyguardShowing();
@@ -5460,11 +5468,12 @@
* appended to any existing file content.
* @param firstPids of dalvik VM processes to dump stack traces for first
* @param lastPids of dalvik VM processes to dump stack traces for last
- * @param nativeProcs optional list of native process names to dump stack crawls
+ * @param nativePids optional list of native pids to dump stack crawls
* @return file containing stack traces, or null if no dump file is configured
*/
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+ ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+ ArrayList<Integer> nativePids) {
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath == null || tracesPath.length() == 0) {
return null;
@@ -5480,7 +5489,7 @@
return null;
}
- dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
+ dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativePids);
return tracesFile;
}
@@ -5522,7 +5531,8 @@
}
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+ ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+ ArrayList<Integer> nativePids) {
// Use a FileObserver to detect when traces finish writing.
// The order of traces is considered important to maintain for legibility.
DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
@@ -5543,18 +5553,15 @@
}
// Next collect the stacks of the native pids
- if (nativeProcs != null) {
- int[] pids = Process.getPidsForCommands(nativeProcs);
- if (pids != null) {
- for (int pid : pids) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
- final long sime = SystemClock.elapsedRealtime();
+ if (nativePids != null) {
+ for (int pid : nativePids) {
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
+ final long sime = SystemClock.elapsedRealtime();
- Debug.dumpNativeBacktraceToFileTimeout(
- pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
- if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
- + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
- }
+ Debug.dumpNativeBacktraceToFileTimeout(
+ pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
+ if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
+ + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
}
@@ -13808,6 +13815,8 @@
final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
final boolean forceResizable = Settings.Global.getInt(
resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
+ final long waitForNetworkTimeoutMs = Settings.Global.getLong(resolver,
+ NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS);
final boolean supportsLeanbackOnly =
mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK_ONLY);
@@ -13863,6 +13872,7 @@
mFullscreenThumbnailScale = res.getFraction(
com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
}
+ mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
}
}
@@ -22516,6 +22526,9 @@
@VisibleForTesting
@GuardedBy("this")
void incrementProcStateSeqAndNotifyAppsLocked() {
+ if (mWaitForNetworkTimeoutMs <= 0) {
+ return;
+ }
// Used for identifying which uids need to block for network.
ArrayList<Integer> blockingUids = null;
for (int i = mActiveUids.size() - 1; i >= 0; --i) {
@@ -23558,10 +23571,14 @@
}
final long startTime = SystemClock.uptimeMillis();
record.waitingForNetwork = true;
- record.lock.wait(WAIT_FOR_NETWORK_TIMEOUT_MS);
+ record.lock.wait(mWaitForNetworkTimeoutMs);
record.waitingForNetwork = false;
final long totalTime = SystemClock.uptimeMillis() - startTime;
- if (DEBUG_NETWORK || totalTime > WAIT_FOR_NETWORK_TIMEOUT_MS / 2) {
+ if (totalTime >= mWaitForNetworkTimeoutMs) {
+ Slog.wtf(TAG_NETWORK, "Total time waited for network rules to get updated: "
+ + totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ + procStateSeq);
+ } else if (DEBUG_NETWORK || totalTime >= mWaitForNetworkTimeoutMs / 2) {
Slog.d(TAG_NETWORK, "Total time waited for network rules to get updated: "
+ totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ procStateSeq);
@@ -23856,6 +23873,8 @@
@VisibleForTesting
public static class Injector {
+ private NetworkManagementInternal mNmi;
+
public AppOpsService getAppOpsService(File file, Handler handler) {
return new AppOpsService(file, handler);
}
@@ -23865,8 +23884,17 @@
}
public boolean isNetworkRestrictedForUid(int uid) {
- // TODO: add implementation
+ if (ensureHasNetworkManagementInternal()) {
+ return mNmi.isNetworkRestrictedForUid(uid);
+ }
return false;
}
+
+ private boolean ensureHasNetworkManagementInternal() {
+ if (mNmi == null) {
+ mNmi = LocalServices.getService(NetworkManagementInternal.class);
+ }
+ return mNmi != null;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 04a09fe..5edfb06 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -10,8 +10,8 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_ACTIVITY_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -313,7 +313,7 @@
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.launchedActivity.packageName);
builder.setType(type);
- builder.addTaggedData(APP_TRANSITION_ACTIVITY_NAME, info.launchedActivity.info.name);
+ builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
if (info.launchedActivity.launchedFromPackage != null) {
builder.addTaggedData(APP_TRANSITION_CALLING_PACKAGE_NAME,
info.launchedActivity.launchedFromPackage);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 6cea483..9a1cd8c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -47,6 +47,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.res.Configuration.EMPTY;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
@@ -83,6 +84,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
@@ -130,7 +132,7 @@
/**
* An entry in the history stack, representing an activity.
*/
-final class ActivityRecord implements AppWindowContainerListener {
+final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_AM;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_SAVED_STATE = TAG + POSTFIX_SAVED_STATE;
@@ -286,11 +288,19 @@
// on the window.
int mRotationAnimationHint = -1;
+ // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+ // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which
+ // directly affects the configuration. We should probably move this into that class and have it
+ // handle calculating override configuration from the bounds.
+ private final Rect mBounds = new Rect();
+
/**
* Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)}
*/
private final Configuration mTmpConfig1 = new Configuration();
private final Configuration mTmpConfig2 = new Configuration();
+ private final Point mTmpPoint = new Point();
+ private final Rect mTmpBounds = new Rect();
private static String startingWindowStateToString(int state) {
switch (state) {
@@ -344,6 +354,13 @@
pw.println(mLastReportedConfiguration);
pw.print(prefix); pw.print("mLastReportedOverrideConfiguration=");
pw.println(mLastReportedOverrideConfiguration);
+ pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration());
+ if (!getOverrideConfiguration().equals(EMPTY)) {
+ pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration());
+ }
+ if (!mBounds.isEmpty()) {
+ pw.println(prefix + "mBounds=" + mBounds);
+ }
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
pw.print(" resultWho="); pw.print(resultWho);
@@ -461,10 +478,15 @@
}
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
- pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+ if (info.supportsPictureInPicture()) {
+ pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+ pw.println(prefix + "supportsPictureInPictureWhilePausing: "
+ + supportsPictureInPictureWhilePausing);
+ }
+ if (info.maxAspectRatio != 0) {
+ pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
+ }
}
- pw.println(prefix + "supportsPictureInPictureWhilePausing: "
- + supportsPictureInPictureWhilePausing);
}
private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -579,6 +601,22 @@
return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
}
+ @Override
+ protected int getChildCount() {
+ // {@link ActivityRecord} is a leaf node and has no children.
+ return 0;
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return null;
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return task;
+ }
+
static class Token extends IApplicationToken.Stub {
private final WeakReference<ActivityRecord> weakActivity;
@@ -764,15 +802,20 @@
inHistory = true;
- task.updateOverrideConfigurationFromLaunchBounds();
final TaskWindowContainerController taskController = task.getWindowContainerController();
+ // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration()
+ task.updateOverrideConfigurationFromLaunchBounds();
+ // Make sure override configuration is up-to-date before using to create window controller.
+ updateOverrideConfiguration();
+
mWindowContainerController = new AppWindowContainerController(taskController, appToken,
this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
appInfo.targetSdkVersion, mRotationAnimationHint,
- ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
+ ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L,
+ getOverrideConfiguration(), mBounds);
task.addActivityToTop(this);
@@ -1994,8 +2037,73 @@
}
/** Call when override config was sent to the Window Manager to update internal records. */
+ // TODO(b/36505427): Why do we set last reported based on sending the config to WM? Seems like
+ // we should only set this when we actually report to the activity which is what the method
+ // setLastReportedMergedOverrideConfiguration() does. Investigate if this is really needed.
void onOverrideConfigurationSent() {
- mLastReportedOverrideConfiguration.setTo(task.getMergedOverrideConfiguration());
+ mLastReportedOverrideConfiguration.setTo(getMergedOverrideConfiguration());
+ }
+
+ @Override
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ super.onOverrideConfigurationChanged(overrideConfiguration);
+ if (mWindowContainerController != null) {
+ mWindowContainerController.onOverrideConfigurationChanged(
+ overrideConfiguration, mBounds);
+ // TODO(b/36505427): Can we consolidate the call points of onOverrideConfigurationSent()
+ // to just use this method instead?
+ onOverrideConfigurationSent();
+ }
+ }
+
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ private boolean updateOverrideConfiguration() {
+ computeBounds(mTmpBounds);
+ if (mTmpBounds.equals(mBounds)) {
+ return false;
+ }
+ mBounds.set(mTmpBounds);
+ // Bounds changed...update configuration to match.
+ mTmpConfig1.unset();
+ task.computeOverrideConfiguration(mTmpConfig1, mBounds, null /* insetBounds */,
+ false /* overrideWidth */, false /* overrideHeight */);
+ onOverrideConfigurationChanged(mTmpConfig1);
+ return true;
+ }
+
+ /** Computes the override configuration for this activity */
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ private void computeBounds(Rect outBounds) {
+ outBounds.setEmpty();
+ final float maxAspectRatio = info.maxAspectRatio;
+ final ActivityStack stack = getStack();
+ if ((task != null && !task.mFullscreen) || maxAspectRatio == 0 || stack == null) {
+ // We don't set override configuration if that activity task isn't fullscreen. I.e. the
+ // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
+ // the activity.
+ return;
+ }
+
+ stack.getDisplaySize(mTmpPoint);
+ int maxActivityWidth = mTmpPoint.x;
+ int maxActivityHeight = mTmpPoint.y;
+ if (mTmpPoint.x < mTmpPoint.y) {
+ // Width is the shorter side, so we use that to figure-out what the max. height should
+ // be given the aspect ratio.
+ maxActivityHeight = (int) ((maxActivityWidth * maxAspectRatio) + 0.5f);
+ } else {
+ // Height is the shorter side, so we use that to figure-out what the max. width should
+ // be given the aspect ratio.
+ maxActivityWidth = (int) ((maxActivityHeight * maxAspectRatio) + 0.5f);
+ }
+
+ if (mTmpPoint.x <= maxActivityWidth && mTmpPoint.y <= maxActivityHeight) {
+ // The display matches or is less than the activity aspect ratio, so nothing else to do.
+ return;
+ }
+
+ // Compute configuration based on max supported width and height.
+ outBounds.set(0, 0, maxActivityWidth, maxActivityHeight);
}
/**
@@ -2028,13 +2136,16 @@
if (displayChanged) {
mLastReportedDisplayId = newDisplayId;
}
+ // TODO(b/36505427): Is there a better place to do this?
+ updateOverrideConfiguration();
+
// Short circuit: if the two full configurations are equal (the common case), then there is
// nothing to do. We test the full configuration instead of the global and merged override
// configurations because there are cases (like moving a task to the pinned stack) where
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig1.setTo(mLastReportedConfiguration);
mTmpConfig1.updateFrom(mLastReportedOverrideConfiguration);
- if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
+ if (getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration & display unchanged in " + this);
return true;
@@ -2045,15 +2156,15 @@
// Find changes between last reported merged configuration and the current one. This is used
// to decide whether to relaunch an activity or just report a configuration change.
- final int changes = getTaskConfigurationChanges(mTmpConfig1);
+ final int changes = getConfigurationChanges(mTmpConfig1);
// Update last reported values.
final Configuration newGlobalConfig = service.getGlobalConfiguration();
- final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
+ final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();
mTmpConfig1.setTo(mLastReportedConfiguration);
mTmpConfig2.setTo(mLastReportedOverrideConfiguration);
mLastReportedConfiguration.setTo(newGlobalConfig);
- mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);
+ mLastReportedOverrideConfiguration.setTo(newMergedOverrideConfig);
if (changes == 0 && !forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
@@ -2061,9 +2172,9 @@
// There are no significant differences, so we won't relaunch but should still deliver
// the new configuration to the client process.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
- scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ scheduleConfigurationChanged(newMergedOverrideConfig);
}
return true;
}
@@ -2088,9 +2199,9 @@
+ Integer.toHexString(changes) + ", handles=0x"
+ Integer.toHexString(info.getRealConfigChanged())
+ ", newGlobalConfig=" + newGlobalConfig
- + ", newTaskMergedOverrideConfig=" + newTaskMergedOverrideConfig);
+ + ", newMergedOverrideConfig=" + newMergedOverrideConfig);
- if (shouldRelaunchLocked(changes, newGlobalConfig, newTaskMergedOverrideConfig)
+ if (shouldRelaunchLocked(changes, newGlobalConfig, newMergedOverrideConfig)
|| forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
@@ -2133,13 +2244,13 @@
}
// Default case: the activity can handle this new configuration, so hand it over.
- // NOTE: We only forward the task override configuration as the system level configuration
+ // NOTE: We only forward the override configuration as the system level configuration
// changes is always sent to all processes when they happen so it can just use whatever
// system level configuration it last got.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
- scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ scheduleConfigurationChanged(newMergedOverrideConfig);
}
stopFreezingScreenLocked(false);
@@ -2167,31 +2278,31 @@
return (changes&(~configChanged)) != 0;
}
- private int getTaskConfigurationChanges(Configuration lastReportedConfig) {
+ private int getConfigurationChanges(Configuration lastReportedConfig) {
// Determine what has changed. May be nothing, if this is a config that has come back from
// the app after going idle. In that case we just want to leave the official config object
// now in the activity and do nothing else.
- final Configuration currentConfig = task.getConfiguration();
- int taskChanges = lastReportedConfig.diff(currentConfig);
+ final Configuration currentConfig = getConfiguration();
+ int changes = lastReportedConfig.diff(currentConfig);
// We don't want to use size changes if they don't cross boundaries that are important to
// the app.
- if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
+ if ((changes & CONFIG_SCREEN_SIZE) != 0) {
final boolean crosses = crossesHorizontalSizeThreshold(lastReportedConfig.screenWidthDp,
currentConfig.screenWidthDp)
|| crossesVerticalSizeThreshold(lastReportedConfig.screenHeightDp,
currentConfig.screenHeightDp);
if (!crosses) {
- taskChanges &= ~CONFIG_SCREEN_SIZE;
+ changes &= ~CONFIG_SCREEN_SIZE;
}
}
- if ((taskChanges & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+ if ((changes & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
final int oldSmallest = lastReportedConfig.smallestScreenWidthDp;
final int newSmallest = currentConfig.smallestScreenWidthDp;
if (!crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
- taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
+ changes &= ~CONFIG_SMALLEST_SCREEN_SIZE;
}
}
- return taskChanges;
+ return changes;
}
private static boolean isResizeOnlyChange(int change) {
@@ -2232,7 +2343,7 @@
app.thread.scheduleRelaunchActivity(appToken, pendingResults, pendingNewIntents,
configChangeFlags, !andResume,
new Configuration(service.getGlobalConfiguration()),
- new Configuration(task.getMergedOverrideConfiguration()), preserveWindow);
+ new Configuration(getMergedOverrideConfiguration()), preserveWindow);
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
// pass in 'andResume' if this activity is currently resumed, which implies we aren't
// sleeping.
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9a4f804..28a4e1a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -759,6 +759,12 @@
}
}
+ final void removeActivitiesFromLRUListLocked(TaskRecord task) {
+ for (ActivityRecord r : task.mActivities) {
+ mLRUActivities.remove(r);
+ }
+ }
+
final boolean updateLRUListLocked(ActivityRecord r) {
final boolean hadit = mLRUActivities.remove(r);
mLRUActivities.add(r);
@@ -4947,6 +4953,7 @@
}
}
mTaskHistory.remove(task);
+ removeActivitiesFromLRUListLocked(task);
updateTaskMovement(task, true);
if (mode == REMOVE_TASK_MODE_DESTROYING && task.mActivities.isEmpty()) {
@@ -5114,6 +5121,7 @@
// Apps may depend on onResume()/onPause() being called in pairs.
if (setResume) {
mResumedActivity = r;
+ updateLRUListLocked(r);
}
// If the activity was previously pausing, then ensure we transfer that as well
if (setPause) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 97d0aa3..8559dca 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1380,7 +1380,7 @@
new Configuration(mService.getGlobalConfiguration());
r.setLastReportedGlobalConfiguration(globalConfiguration);
final Configuration mergedOverrideConfiguration =
- new Configuration(task.getMergedOverrideConfiguration());
+ new Configuration(r.getMergedOverrideConfiguration());
r.setLastReportedMergedOverrideConfiguration(mergedOverrideConfiguration);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
@@ -3555,8 +3555,16 @@
stackHeader.append(" mFullscreen=" + stack.mFullscreen);
stackHeader.append("\n");
stackHeader.append(" mBounds=" + stack.mBounds);
- printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
- needSep, stackHeader.toString());
+
+ final boolean printedStackHeader = stack.dumpActivitiesLocked(fd, pw, dumpAll,
+ dumpClient, dumpPackage, needSep, stackHeader.toString());
+ printed |= printedStackHeader;
+ if (!printedStackHeader) {
+ // Ensure we always dump the stack header even if there are no activities
+ pw.println();
+ pw.println(stackHeader);
+ }
+
printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, " ", "Run", false,
!dumpAll, false, dumpPackage, true,
" Running activities (most recent first):", null);
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1b7b225..4bd06b7 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1790,21 +1790,7 @@
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
- if (mLaunchBounds != null) {
- mInTask.updateOverrideConfiguration(mLaunchBounds);
- int stackId = mInTask.getLaunchStackId();
- if (stackId != mInTask.getStackId()) {
- mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
- DEFER_RESUME, "inTaskToFront");
- stackId = mInTask.getStackId();
- }
- if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
- }
- }
mTargetStack = mInTask.getStack();
- mTargetStack.moveTaskToFrontLocked(
- mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
// Check whether we should actually launch the new activity in to the task,
// or just reuse the current activity on top.
@@ -1813,6 +1799,8 @@
&& top.userId == mStartActivity.userId) {
if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| mLaunchSingleTop || mLaunchSingleTask) {
+ mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, "inTaskToFront");
ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and the client said not to do
@@ -1826,12 +1814,31 @@
}
if (!mAddingToTask) {
+ mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, "inTaskToFront");
// We don't actually want to have this activity added to the task, so just
// stop here but still tell the caller that we consumed the intent.
ActivityOptions.abort(mOptions);
return START_TASK_TO_FRONT;
}
+ if (mLaunchBounds != null) {
+ mInTask.updateOverrideConfiguration(mLaunchBounds);
+ int stackId = mInTask.getLaunchStackId();
+ if (stackId != mInTask.getStackId()) {
+ mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ DEFER_RESUME, "inTaskToFront");
+ stackId = mInTask.getStackId();
+ mTargetStack = mInTask.getStack();
+ }
+ if (StackId.resizeStackWithLaunchBounds(stackId)) {
+ mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+ }
+ }
+
+ mTargetStack.moveTaskToFrontLocked(
+ mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
+
addOrReparentStartingActivity(mInTask, "setTaskFromInTask");
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
+ " in explicit task " + mStartActivity.task);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index f927cce..7b1af38 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -870,12 +870,22 @@
nativeProcs = NATIVE_STACKS_OF_INTEREST;
}
+ int[] pids = Process.getPidsForCommands(nativeProcs);
+ ArrayList<Integer> nativePids = null;
+
+ if (pids != null) {
+ nativePids = new ArrayList<Integer>(pids.length);
+ for (int i : pids) {
+ nativePids.add(i);
+ }
+ }
+
// For background ANRs, don't pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
File tracesFile = mService.dumpStackTraces(true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
- nativeProcs);
+ nativePids);
String cpuInfo = null;
if (ActivityManagerService.MONITOR_CPU_USAGE) {
diff --git a/services/core/java/com/android/server/am/ConfigurationContainer.java b/services/core/java/com/android/server/am/ConfigurationContainer.java
index a3e95b8..3d60681 100644
--- a/services/core/java/com/android/server/am/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/am/ConfigurationContainer.java
@@ -22,6 +22,8 @@
* Contains common logic for classes that have override configurations and are organized in a
* hierarchy.
*/
+// TODO(b/36505427): Move to wm package and have WindowContainer use this instead of having its own
+// implementation for merging configuration.
abstract class ConfigurationContainer<E extends ConfigurationContainer> {
/** Contains override configuration settings applied to this configuration container. */
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a668fea..fd65c10 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -924,12 +924,12 @@
@Override
protected int getChildCount() {
- return 0;
+ return mActivities.size();
}
@Override
protected ConfigurationContainer getChildAt(int index) {
- return null;
+ return mActivities.get(index);
}
@Override
@@ -944,7 +944,7 @@
}
// Close up recents linked list.
- void closeRecentsChain() {
+ private void closeRecentsChain() {
if (mPrevAffiliate != null) {
mPrevAffiliate.setNextAffiliate(mNextAffiliate);
}
@@ -1188,7 +1188,10 @@
throw new IllegalArgumentException("Can not add r=" + " to task=" + this
+ " current parent=" + r.task);
}
+ // TODO(b/36505427): Maybe make task private to ActivityRecord so we can also do
+ // onParentChanged() within the setter?
r.task = this;
+ r.onParentChanged();
// Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
if (!mActivities.remove(r) && r.fullscreen) {
@@ -1995,7 +1998,7 @@
if (mStack == null || StackId.persistTaskBounds(mStack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
- calculateOverrideConfig(newConfig, mTmpRect, insetBounds,
+ computeOverrideConfiguration(newConfig, mTmpRect, insetBounds,
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
onOverrideConfigurationChanged(newConfig);
@@ -2008,7 +2011,10 @@
}
/** Clears passed config and fills it with new override values. */
- private void calculateOverrideConfig(Configuration config, Rect bounds, Rect insetBounds,
+ // TODO(b/36505427): TaskRecord.computeOverrideConfiguration() is a utility method that doesn't
+ // depend on task or stacks, but uses those object to get the display to base the calculation
+ // on. Probably best to centralize calculations like this in ConfigurationContainer.
+ void computeOverrideConfiguration(Configuration config, Rect bounds, Rect insetBounds,
boolean overrideWidth, boolean overrideHeight) {
mTmpNonDecorBounds.set(bounds);
mTmpStableBounds.set(bounds);
@@ -2027,7 +2033,7 @@
config.smallestScreenWidthDp =
mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
config.screenWidthDp = config.screenHeightDp = config.smallestScreenWidthDp;
- Slog.wtf(TAG, "Expected stack when caclulating override config");
+ Slog.wtf(TAG, "Expected stack when calculating override config");
}
config.orientation = (config.screenWidthDp <= config.screenHeightDp)
@@ -2048,23 +2054,6 @@
}
- /**
- * Using the existing configuration {@param config}, creates a new task override config such
- * that all the fields that are usually set in an override config are set to the ones in
- * {@param config}.
- */
- Configuration extractOverrideConfig(Configuration config) {
- final Configuration extracted = new Configuration();
- extracted.screenWidthDp = config.screenWidthDp;
- extracted.screenHeightDp = config.screenHeightDp;
- extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
- extracted.orientation = config.orientation;
- // We're only overriding LONG, SIZE and COMPAT parts of screenLayout.
- extracted.screenLayout = config.screenLayout & (Configuration.SCREENLAYOUT_LONG_MASK
- | Configuration.SCREENLAYOUT_SIZE_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED);
- return extracted;
- }
-
Rect updateOverrideConfigurationFromLaunchBounds() {
final Rect bounds = validateBounds(getLaunchBounds());
updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f5ea74d..8cc9375 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -842,7 +842,7 @@
/** Component used to install ephemeral applications */
ComponentName mInstantAppInstallerComponent;
- final ActivityInfo mInstantAppInstallerActivity = new ActivityInfo();
+ ActivityInfo mInstantAppInstallerActivity;
final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
@@ -2931,13 +2931,16 @@
private void updateInstantAppInstallerLocked() {
final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent;
- final ComponentName newInstantAppInstallerComponent = getEphemeralInstallerLPr();
+ final ActivityInfo newInstantAppInstaller = getEphemeralInstallerLPr();
+ ComponentName newInstantAppInstallerComponent = newInstantAppInstaller == null
+ ? null : newInstantAppInstaller.getComponentName();
+
if (newInstantAppInstallerComponent != null
&& !newInstantAppInstallerComponent.equals(oldInstantAppInstallerComponent)) {
if (DEBUG_EPHEMERAL) {
Slog.d(TAG, "Set ephemeral installer: " + newInstantAppInstallerComponent);
}
- setUpInstantAppInstallerActivityLP(newInstantAppInstallerComponent);
+ setUpInstantAppInstallerActivityLP(newInstantAppInstaller);
} else if (DEBUG_EPHEMERAL && newInstantAppInstallerComponent == null) {
Slog.d(TAG, "Unset ephemeral installer; none available");
}
@@ -3160,7 +3163,7 @@
return null;
}
- private @Nullable ComponentName getEphemeralInstallerLPr() {
+ private @Nullable ActivityInfo getEphemeralInstallerLPr() {
final Intent intent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
@@ -3186,7 +3189,7 @@
if (matches.size() == 0) {
return null;
} else if (matches.size() == 1) {
- return matches.get(0).getComponentInfo().getComponentName();
+ return (ActivityInfo) matches.get(0).getComponentInfo();
} else {
throw new RuntimeException(
"There must be at most one ephemeral installer; found " + matches);
@@ -10642,28 +10645,23 @@
}
}
- private void setUpInstantAppInstallerActivityLP(ComponentName installerComponent) {
- if (installerComponent == null) {
+ private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) {
+ if (installerActivity == null) {
if (DEBUG_EPHEMERAL) {
Slog.d(TAG, "Clear ephemeral installer activity");
}
- mInstantAppInstallerActivity.applicationInfo = null;
+ mInstantAppInstallerActivity = null;
return;
}
if (DEBUG_EPHEMERAL) {
- Slog.d(TAG, "Set ephemeral installer activity: " + installerComponent);
+ Slog.d(TAG, "Set ephemeral installer activity: "
+ + installerActivity.getComponentName());
}
- final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName());
// Set up information for ephemeral installer activity
- mInstantAppInstallerActivity.applicationInfo = pkg.applicationInfo;
- mInstantAppInstallerActivity.name = installerComponent.getClassName();
- mInstantAppInstallerActivity.packageName = pkg.applicationInfo.packageName;
- mInstantAppInstallerActivity.processName = pkg.applicationInfo.packageName;
- mInstantAppInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
- mInstantAppInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+ mInstantAppInstallerActivity = installerActivity;
+ mInstantAppInstallerActivity.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
| ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
- mInstantAppInstallerActivity.theme = 0;
mInstantAppInstallerActivity.exported = true;
mInstantAppInstallerActivity.enabled = true;
mInstantAppInstallerInfo.activityInfo = mInstantAppInstallerActivity;
diff --git a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
index cd55f50..7d53310 100644
--- a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
+++ b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
@@ -27,6 +27,7 @@
import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -58,6 +59,9 @@
private final Context mContext;
private AlertDialog mAlertDialog;
private boolean mIsShortcutEnabled;
+ private boolean mEnabledOnLockScreen;
+ private int mUserId;
+
// Visible for testing
public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
@@ -72,29 +76,55 @@
return context.getString(R.string.config_defaultAccessibilityService);
}
- public AccessibilityShortcutController(Context context, Handler handler) {
+ public AccessibilityShortcutController(Context context, Handler handler, int initialUserId) {
mContext = context;
- // Keep track of state of shortcut
+ // Keep track of state of shortcut settings
+ final ContentObserver co = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (userId == mUserId) {
+ onSettingsChanged();
+ }
+ }
+ };
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
- false,
- new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- onSettingsChanged();
- }
- },
- UserHandle.USER_ALL);
- updateShortcutEnabled();
+ false, co, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED),
+ false, co, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN),
+ false, co, UserHandle.USER_ALL);
+ setCurrentUser(mUserId);
}
- public boolean isAccessibilityShortcutAvailable() {
- return mIsShortcutEnabled;
+ public void setCurrentUser(int currentUserId) {
+ mUserId = currentUserId;
+ onSettingsChanged();
+ }
+
+ /**
+ * Check if the shortcut is available.
+ *
+ * @param onLockScreen Whether or not the phone is currently locked.
+ *
+ * @return {@code true} if the shortcut is available
+ */
+ public boolean isAccessibilityShortcutAvailable(boolean phoneLocked) {
+ return mIsShortcutEnabled && (!phoneLocked || mEnabledOnLockScreen);
}
public void onSettingsChanged() {
- updateShortcutEnabled();
+ final boolean haveValidService =
+ !TextUtils.isEmpty(getTargetServiceComponentNameString(mContext, mUserId));
+ final ContentResolver cr = mContext.getContentResolver();
+ final boolean enabled = Settings.Secure.getIntForUser(
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
+ mEnabledOnLockScreen = Settings.Secure.getIntForUser(
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, 0, mUserId) == 1;
+ mIsShortcutEnabled = enabled && haveValidService;
}
/**
@@ -171,11 +201,6 @@
}
}
- private void updateShortcutEnabled() {
- mIsShortcutEnabled = !TextUtils.isEmpty(getTargetServiceComponentNameString(
- mContext, UserHandle.myUserId()));
- }
-
private AlertDialog createShortcutWarningDialog(int userId) {
final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 31e22b9..52f6955 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1547,7 +1547,7 @@
}
private void interceptAccessibilityShortcutChord() {
- if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+ if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(isKeyguardLocked())
&& mScreenshotChordVolumeDownKeyTriggered && mA11yShortcutChordVolumeUpKeyTriggered
&& !mScreenshotChordPowerKeyTriggered) {
final long now = SystemClock.uptimeMillis();
@@ -1771,7 +1771,7 @@
mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
mAccessibilityShortcutController =
- new AccessibilityShortcutController(mContext, new Handler());
+ new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
// Init display burn-in protection
boolean burnInProtectionEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_enableBurnInProtection);
@@ -3243,7 +3243,7 @@
// If an accessibility shortcut might be partially complete, hold off dispatching until we
// know if it is complete or not
- if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+ if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(false)
&& (flags & KeyEvent.FLAG_FALLBACK) == 0) {
if (mScreenshotChordVolumeDownKeyTriggered ^ mA11yShortcutChordVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
@@ -5823,9 +5823,7 @@
mScreenshotChordVolumeDownKeyConsumed = false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
- if (!isKeyguardLocked()) {
- interceptAccessibilityShortcutChord();
- }
+ interceptAccessibilityShortcutChord();
}
} else {
mScreenshotChordVolumeDownKeyTriggered = false;
@@ -5841,9 +5839,7 @@
mA11yShortcutChordVolumeUpKeyConsumed = false;
cancelPendingPowerKeyAction();
cancelPendingScreenshotChordAction();
- if (!isKeyguardLocked()) {
- interceptAccessibilityShortcutChord();
- }
+ interceptAccessibilityShortcutChord();
}
} else {
mA11yShortcutChordVolumeUpKeyTriggered = false;
@@ -7945,6 +7941,9 @@
if (mKeyguardDelegate != null) {
mKeyguardDelegate.setCurrentUser(newUserId);
}
+ if (mAccessibilityShortcutController != null) {
+ mAccessibilityShortcutController.setCurrentUser(newUserId);
+ }
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
if (statusBar != null) {
statusBar.setCurrentUser(newUserId);
diff --git a/services/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
index 8f95cc7..a8d6223 100644
--- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java
+++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
@@ -1,4 +1,3 @@
-
package com.android.server.vr;
import static android.view.Display.INVALID_DISPLAY;
@@ -8,14 +7,18 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.ImageFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.service.vr.IVrStateCallbacks;
+import android.os.SystemProperties;
+import android.service.vr.IPersistentVrStateCallbacks;
import android.service.vr.IVrManager;
import android.util.Log;
import android.view.Surface;
@@ -34,11 +37,12 @@
private final static int HEIGHT = 1800;
private final static int WIDTH = 1400;
private final static int DPI = 320;
+ private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000;
private final static String DEBUG_ACTION_SET_MODE =
"com.android.server.vr.CompatibilityDisplay.SET_MODE";
private final static String DEBUG_EXTRA_MODE_ON =
- "com.android.servier.vr.CompatibilityDisplay.EXTRA_MODE_ON";
+ "com.android.server.vr.CompatibilityDisplay.EXTRA_MODE_ON";
private final static String DEBUG_ACTION_SET_SURFACE =
"com.android.server.vr.CompatibilityDisplay.SET_SURFACE";
private final static String DEBUG_EXTRA_SURFACE =
@@ -46,14 +50,16 @@
private final DisplayManager mDisplayManager;
private final IVrManager mVrManager;
+ private final Object mVdLock = new Object();
+ private final Handler mHandler = new Handler();
- // TODO: Lock initially created when VrStateCallback was connected through Binder. This may not
- // be necessary with the direct access to VrManager.
- private final Object vdLock = new Object();
-
- private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ /**
+ * Callback implementation to receive changes to VrMode.
+ **/
+ private final IPersistentVrStateCallbacks mVrStateCallbacks =
+ new IPersistentVrStateCallbacks.Stub() {
@Override
- public void onVrStateChanged(boolean enabled) {
+ public void onPersistentVrStateChanged(boolean enabled) {
if (enabled != mIsVrModeEnabled) {
mIsVrModeEnabled = enabled;
updateVirtualDisplay();
@@ -63,7 +69,9 @@
private VirtualDisplay mVirtualDisplay;
private Surface mSurface;
- private boolean mIsDebugOverrideEnabled;
+ private ImageReader mImageReader;
+ private Runnable mStopVDRunnable;
+ private boolean mIsVrModeOverrideEnabled;
private boolean mIsVrModeEnabled;
public CompatibilityDisplay(DisplayManager displayManager, IVrManager vrManager) {
@@ -79,13 +87,23 @@
startDebugOnlyBroadcastReceiver(context);
}
+ /**
+ * Creates and Destroys the virtual display depending on the current state of VrMode.
+ */
private void updateVirtualDisplay() {
- if (mIsVrModeEnabled || (DEBUG && mIsDebugOverrideEnabled)) {
+ boolean createVirtualDisplay = "true".equals(SystemProperties.get("vr_virtualdisplay"));
+ if (DEBUG) {
+ Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", createVD: " + createVirtualDisplay +
+ ", override: " + mIsVrModeOverrideEnabled);
+ }
+
+ if (mIsVrModeEnabled || (createVirtualDisplay && mIsVrModeOverrideEnabled)) {
// TODO: Consider not creating the display until ActivityManager needs one on
// which to display a 2D application.
- // TODO: STOPSHIP Remove DEBUG conditional before launching.
- if (DEBUG) {
+ // TODO: STOPSHIP Remove createVirtualDisplay conditional before launching.
+ if (createVirtualDisplay) {
startVirtualDisplay();
+ startImageReader();
}
} else {
// Stop virtual display to test exit condition
@@ -93,8 +111,17 @@
}
}
+ /**
+ * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and
+ * set a custom Surface for the virtual display. This allows testing of the virtual display
+ * without going into full 3D.
+ *
+ * @param context The context.
+ */
private void startDebugOnlyBroadcastReceiver(Context context) {
- if (DEBUG) {
+ // STOPSHIP: remove vr_debug_vd_receiver test.
+ boolean debugBroadcast = "true".equals(SystemProperties.get("vr_debug_vd_receiver"));
+ if (DEBUG || debugBroadcast) {
IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE);
intentFilter.addAction(DEBUG_ACTION_SET_SURFACE);
@@ -103,21 +130,13 @@
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (DEBUG_ACTION_SET_MODE.equals(action)) {
- mIsDebugOverrideEnabled =
+ mIsVrModeOverrideEnabled =
intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false);
updateVirtualDisplay();
} else if (DEBUG_ACTION_SET_SURFACE.equals(action)) {
if (mVirtualDisplay != null) {
- final Surface newSurface =
- intent.getParcelableExtra(DEBUG_EXTRA_SURFACE);
-
- Log.i(TAG, "Setting the new surface from " + mSurface + " to " + newSurface);
- if (newSurface != mSurface) {
- mVirtualDisplay.setSurface(newSurface);
- if (mSurface != null) {
- mSurface.release();
- }
- mSurface = newSurface;
+ if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) {
+ setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE));
}
} else {
Log.w(TAG, "Cannot set the surface because the VD is null.");
@@ -128,18 +147,27 @@
}
}
+ /**
+ * Starts listening to VrMode changes.
+ */
private void startVrModeListener() {
if (mVrManager != null) {
try {
- mVrManager.registerListener(mVrStateCallbacks);
+ mVrManager.registerPersistentVrStateListener(mVrStateCallbacks);
} catch (RemoteException e) {
Log.e(TAG, "Could not register VR State listener.", e);
}
}
}
+ /**
+ * Returns the virtual display ID if one currently exists, otherwise returns
+ * {@link INVALID_DISPLAY_ID}.
+ *
+ * @return The virtual display ID.
+ */
public int getVirtualDisplayId() {
- synchronized(vdLock) {
+ synchronized(mVdLock) {
if (mVirtualDisplay != null) {
int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
if (DEBUG) {
@@ -151,6 +179,9 @@
return INVALID_DISPLAY;
}
+ /**
+ * Starts the virtual display if one does not already exist.
+ */
private void startVirtualDisplay() {
if (DEBUG) {
Log.d(TAG, "Request to start VD, DM:" + mDisplayManager);
@@ -161,7 +192,7 @@
return;
}
- synchronized (vdLock) {
+ synchronized (mVdLock) {
if (mVirtualDisplay != null) {
Log.i(TAG, "VD already exists, ignoring request");
return;
@@ -169,10 +200,6 @@
mVirtualDisplay = mDisplayManager.createVirtualDisplay("VR 2D Display", WIDTH, HEIGHT,
DPI, null /* Surface */, 0 /* flags */);
- if (mVirtualDisplay != null && mSurface != null && mSurface.isValid()) {
- // TODO: Need to protect all setSurface calls with a lock.
- mVirtualDisplay.setSurface(mSurface);
- }
}
if (DEBUG) {
@@ -180,16 +207,69 @@
}
}
+ /**
+ * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout.
+ * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out
+ * of being enabled. This can happen sometimes with our 2D test app.
+ */
private void stopVirtualDisplay() {
- if (DEBUG) {
- Log.i(TAG, "Santos, stopping VD");
+ if (mStopVDRunnable == null) {
+ mStopVDRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mIsVrModeEnabled) {
+ Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on.");
+ } else {
+ Log.i(TAG, "Stopping Virtual Display");
+ synchronized (mVdLock) {
+ setSurfaceLocked(null); // clean up and release the surface first.
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }
+ }
+ }
+ }
+ };
}
- synchronized (vdLock) {
+ mHandler.removeCallbacks(mStopVDRunnable);
+ mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS);
+ }
+
+ /**
+ * Set the surface to use with the virtual display.
+ *
+ * Code should be locked by {@link #mVdLock} before invoked.
+ *
+ * @param surface The Surface to set.
+ */
+ private void setSurfaceLocked(Surface surface) {
+ // Change the surface to either a valid surface or a null value.
+ if (mSurface != surface && (surface == null || surface.isValid())) {
+ Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface);
if (mVirtualDisplay != null) {
- mVirtualDisplay.release();
- mVirtualDisplay = null;
+ mVirtualDisplay.setSurface(surface);
}
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ mSurface = surface;
+ }
+ }
+
+ /**
+ * Starts an ImageReader as a do-nothing Surface. The virtual display will not get fully
+ * initialized within surface flinger unless it has a valid Surface associated with it. We use
+ * the ImageReader as the default valid Surface.
+ */
+ private void startImageReader() {
+ if (mImageReader == null) {
+ mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.RAW_PRIVATE,
+ 2 /* maxImages */);
+ }
+ synchronized (mVdLock) {
+ setSurfaceLocked(mImageReader.getSurface());
}
}
}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index ef3d87c..e60295d 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -32,9 +32,11 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Trace;
import android.util.Slog;
import android.view.IApplicationToken;
@@ -180,12 +182,13 @@
IApplicationToken token, AppWindowContainerListener listener, int index,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
- int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
+ int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
+ Configuration overrideConfig, Rect bounds) {
this(taskController, token, listener, index, requestedOrientation, fullscreen,
showForAllUsers,
configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
- WindowManagerService.getInstance());
+ WindowManagerService.getInstance(), overrideConfig, bounds);
}
public AppWindowContainerController(TaskWindowContainerController taskController,
@@ -193,7 +196,7 @@
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
- WindowManagerService service) {
+ WindowManagerService service, Configuration overrideConfig, Rect bounds) {
super(listener, service);
mHandler = new Handler(service.mH.getLooper());
mToken = token;
@@ -214,7 +217,7 @@
atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
- alwaysFocusable, this);
+ alwaysFocusable, this, overrideConfig, bounds);
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+ " controller=" + taskController + " at " + index);
task.addChild(atoken, index);
@@ -226,11 +229,12 @@
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller) {
+ boolean alwaysFocusable, AppWindowContainerController controller,
+ Configuration overrideConfig, Rect bounds) {
return new AppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
- controller);
+ controller, overrideConfig, bounds);
}
public void removeContainer(int displayId) {
@@ -297,6 +301,17 @@
}
}
+ // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
+ // a generic way to set override config. Need to untangle current ways the override config is
+ // currently set for tasks and displays before we are doing that though.
+ public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+ synchronized(mWindowMap) {
+ if (mContainer != null) {
+ mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
+ }
+ }
+ }
+
public void setDisablePreviewScreenshots(boolean disable) {
synchronized (mWindowMap) {
if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d1f1305..72ae90d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -164,6 +164,11 @@
private boolean mLastContainsShowWhenLockedWindow;
private boolean mLastContainsDismissKeyguardWindow;
+ // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+ // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
+ // affects the configuration. We should probably move this into that class.
+ private final Rect mBounds = new Rect();
+
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
@@ -173,8 +178,8 @@
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
- AppWindowContainerController controller) {
- this(service, token, voiceInteraction, dc, fullscreen);
+ AppWindowContainerController controller, Configuration overrideConfig, Rect bounds) {
+ this(service, token, voiceInteraction, dc, fullscreen, overrideConfig, bounds);
setController(controller);
mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
mShowForAllUsers = showForAllUsers;
@@ -191,7 +196,7 @@
}
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
- DisplayContent dc, boolean fillsParent) {
+ DisplayContent dc, boolean fillsParent, Configuration overrideConfig, Rect bounds) {
super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
false /* ownerCanManageAppTokens */);
appToken = token;
@@ -199,6 +204,30 @@
mFillsParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(this);
mAppAnimator = new AppWindowAnimator(this, service);
+ if (overrideConfig != null) {
+ onOverrideConfigurationChanged(overrideConfig);
+ }
+ if (bounds != null) {
+ mBounds.set(bounds);
+ }
+ }
+
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+ onOverrideConfigurationChanged(overrideConfiguration);
+ if (mBounds.equals(bounds)) {
+ return;
+ }
+ // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
+ mBounds.set(bounds);
+ onResize();
+ }
+
+ void getBounds(Rect outBounds) {
+ outBounds.set(mBounds);
+ }
+
+ boolean hasBounds() {
+ return !mBounds.isEmpty();
}
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e5b00f3..8f391a7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3044,6 +3044,12 @@
mTaskStackContainers.removeExistingAppTokensIfPossible();
}
+ @Override
+ void onDescendantOverrideConfigurationChanged() {
+ setLayoutNeeded();
+ mService.requestTraversal();
+ }
+
static final class TaskForResizePointSearchResult {
boolean searchDone;
Task taskForResize;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6973c3c..2a02359 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.EMPTY;
/**
* Defines common functionality for classes that can hold windows directly or through their
@@ -315,9 +316,22 @@
void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
mOverrideConfiguration.setTo(overrideConfiguration);
// Update full configuration of this container and all its children.
- onConfigurationChanged(mParent != null ? mParent.getConfiguration() : Configuration.EMPTY);
+ onConfigurationChanged(mParent != null ? mParent.getConfiguration() : EMPTY);
// Update merged override config of this container and all its children.
onMergedOverrideConfigurationChanged();
+
+ if (mParent != null) {
+ mParent.onDescendantOverrideConfigurationChanged();
+ }
+ }
+
+ /**
+ * Notify that a descendant's overrideConfiguration has changed.
+ */
+ void onDescendantOverrideConfigurationChanged() {
+ if (mParent != null) {
+ mParent.onDescendantOverrideConfigurationChanged();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d4c8b1f..6fd95a4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -717,16 +717,16 @@
mHaveFrame = true;
final Task task = getTask();
- final boolean fullscreenTask = !isInMultiWindowMode();
+ final boolean inFullscreenContainer = inFullscreenContainer();
final boolean windowsAreFloating = task != null && task.isFloating();
final DisplayContent dc = getDisplayContent();
// If the task has temp inset bounds set, we have to make sure all its windows uses
// the temp inset frame. Otherwise different display frames get applied to the main
// window and the child window, making them misaligned.
- if (fullscreenTask) {
+ if (inFullscreenContainer) {
mInsetFrame.setEmpty();
- } else {
+ } else if (task != null && isInMultiWindowMode()) {
task.getTempInsetBounds(mInsetFrame);
}
@@ -740,7 +740,7 @@
// The offset from the layout containing frame to the actual containing frame.
final int layoutXDiff;
final int layoutYDiff;
- if (fullscreenTask || layoutInParentFrame()) {
+ if (inFullscreenContainer || layoutInParentFrame()) {
// We use the parent frame as the containing frame for fullscreen and child windows
mContainingFrame.set(parentFrame);
mDisplayFrame.set(displayFrame);
@@ -749,7 +749,7 @@
layoutXDiff = 0;
layoutYDiff = 0;
} else {
- task.getBounds(mContainingFrame);
+ getContainerBounds(mContainingFrame);
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
// If the bounds are frozen, we still want to translate the window freely and only
@@ -883,7 +883,7 @@
Math.min(mStableFrame.bottom, mFrame.bottom));
}
- if (fullscreenTask && !windowsAreFloating) {
+ if (inFullscreenContainer && !windowsAreFloating) {
// Windows that are not fullscreen can be positioned outside of the display frame,
// but that is not a reason to provide them with overscan insets.
mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
@@ -908,10 +908,10 @@
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 = !windowsAreFloating && !fullscreenTask &&
- mFrame.right > mTmpRect.right;
- boolean overrideBottomInset = !windowsAreFloating && !fullscreenTask &&
- mFrame.bottom > mTmpRect.bottom;
+ boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
+ && mFrame.right > mTmpRect.right;
+ boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
+ && mFrame.bottom > mTmpRect.bottom;
mContentInsets.set(mContentFrame.left - mFrame.left,
mContentFrame.top - mFrame.top,
overrideRightInset ? mTmpRect.right - mContentFrame.right
@@ -3122,6 +3122,28 @@
return task != null && !task.isFullscreen();
}
+ /** Is this window in a container that takes up the entire screen space? */
+ private boolean inFullscreenContainer() {
+ if (mAppToken == null) {
+ return true;
+ }
+ if (mAppToken.hasBounds()) {
+ return false;
+ }
+ return !isInMultiWindowMode();
+ }
+
+ /** Returns the appropriate bounds to use for computing frames. */
+ private void getContainerBounds(Rect outBounds) {
+ if (isInMultiWindowMode()) {
+ getTask().getBounds(outBounds);
+ } else if (mAppToken != null){
+ mAppToken.getBounds(outBounds);
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}
@@ -3137,7 +3159,7 @@
/**
* @return Whether we reported a drag resize change to the application or not already.
*/
- boolean isDragResizingChangeReported() {
+ private boolean isDragResizingChangeReported() {
return mDragResizingChangeReported;
}
@@ -3154,7 +3176,7 @@
* Set whether we got resized but drag resizing flag was false.
* @see #isResizedWhileNotDragResizing().
*/
- void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
+ private void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
mResizedWhileNotDragResizing = resizedWhileNotDragResizing;
mResizedWhileNotDragResizingReported = !resizedWhileNotDragResizing;
}
@@ -3459,17 +3481,17 @@
final int pw = containingFrame.width();
final int ph = containingFrame.height();
final Task task = getTask();
- final boolean nonFullscreenTask = isInMultiWindowMode();
+ final boolean inNonFullscreenContainer = !inFullscreenContainer();
final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
// We need to fit it to the display if either
- // a) The task is fullscreen, or we don't have a task (we assume fullscreen for the taskless
- // windows)
+ // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
+ // for the taskless windows)
// b) If it's a secondary app window, we also need to fit it to the display unless
- // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on screen,
- // but SurfaceViews want to be always at a specific location so we don't fit it to the
- // display.
- final boolean fitToDisplay = (task == null || !nonFullscreenTask)
+ // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
+ // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
+ // the display.
+ final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
|| ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
float x, y;
int w,h;
@@ -3514,7 +3536,7 @@
y = mAttrs.y;
}
- if (nonFullscreenTask && !layoutInParentFrame()) {
+ if (inNonFullscreenContainer && !layoutInParentFrame()) {
// Make sure window fits in containing frame since it is in a non-fullscreen task as
// required by {@link Gravity#apply} call.
w = Math.min(w, pw);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ecbd312..ab86966 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10088,9 +10088,11 @@
return false;
}
- Preconditions.checkNotNull(admin);
synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ if (!isCallerWithSystemUid()) {
+ Preconditions.checkNotNull(admin);
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
return mInjector.securityLogGetLoggingEnabledProperty();
}
}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 59e698c..7b4fa87 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -16,8 +16,6 @@
package android.net.ip;
-import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
-
import com.android.internal.util.MessageUtils;
import com.android.internal.util.WakeupMessage;
@@ -44,7 +42,6 @@
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
-import android.system.OsConstants;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
@@ -1031,36 +1028,15 @@
return true;
}
- private void enableInterfaceIPv6PrivacyExtensions() {
+ private boolean startIPv6() {
+ // Set privacy extensions.
final String PREFER_TEMPADDRS = "2";
- NetdService.run((INetd netd) -> {
- netd.setProcSysNet(
- INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", PREFER_TEMPADDRS);
- });
- }
-
- private void setInterfaceIPv6RaRtInfoMaxPlen(int plen) {
- // Setting RIO max plen is best effort. Catch and ignore most exceptions.
try {
NetdService.run((INetd netd) -> {
- netd.setProcSysNet(
- INetd.IPV6, INetd.CONF, mInterfaceName, "accept_ra_rt_info_max_plen",
- Integer.toString(plen));
- });
- } catch (ServiceSpecificException e) {
- // Old kernel versions without support for RIOs do not export accept_ra_rt_info_max_plen
- // in the /proc filesystem. If the kernel supports RIOs we should never see any other
- // type of error.
- if (e.errorCode != OsConstants.ENOENT) {
- logError("unexpected error setting accept_ra_rt_info_max_plen %s", e);
- }
- }
- }
-
- private boolean startIPv6() {
- try {
- enableInterfaceIPv6PrivacyExtensions();
- setInterfaceIPv6RaRtInfoMaxPlen(RFC7421_PREFIX_LENGTH);
+ netd.setProcSysNet(
+ INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr",
+ PREFER_TEMPADDRS);
+ });
mNwService.enableIpv6(mInterfaceName);
} catch (IllegalStateException|RemoteException|ServiceSpecificException e) {
logError("Unable to change interface settings: %s", e);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java
new file mode 100644
index 0000000..c9180a9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.NetworkPolicyManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.BiFunction;
+
+/**
+ * Test class for {@link NetworkManagementInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.NetworkManagementInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.NetworkManagementInternalTest -w \
+ * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkManagementInternalTest {
+ private static final int TEST_UID = 111;
+
+ private NetworkManagementService.Injector mInjector;
+ private NetworkManagementInternal mNmi;
+
+ @Before
+ public void setUp() {
+ final NetworkManagementService service = new NetworkManagementService();
+ mInjector = service.getInjector();
+ mNmi = service.new LocalService();
+ }
+
+ @Test
+ public void testIsNetworkRestrictedForUid() {
+ // No firewall chains enabled
+ assertFalse(mNmi.isNetworkRestrictedForUid(TEST_UID));
+
+ // Restrict usage of mobile data in background
+ mInjector.setUidOnMeteredNetworkList(true, TEST_UID, true);
+ assertTrue("Should be true since mobile data usage is restricted",
+ mNmi.isNetworkRestrictedForUid(TEST_UID));
+ mInjector.reset();
+
+ // Data saver is on and uid is not whitelisted
+ mInjector.setDataSaverMode(true);
+ mInjector.setUidOnMeteredNetworkList(false, TEST_UID, false);
+ assertTrue("Should be true since data saver is on and the uid is not whitelisted",
+ mNmi.isNetworkRestrictedForUid(TEST_UID));
+ mInjector.reset();
+
+ // Data saver is on and uid is whitelisted
+ mInjector.setDataSaverMode(true);
+ mInjector.setUidOnMeteredNetworkList(false, TEST_UID, true);
+ assertFalse("Should be false since data saver is on and the uid is whitelisted",
+ mNmi.isNetworkRestrictedForUid(TEST_UID));
+ mInjector.reset();
+
+ final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+ // Dozable chain
+ final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+ isRestrictedForDozable.put(FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForDozable.put(FIREWALL_RULE_ALLOW, false);
+ isRestrictedForDozable.put(FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+ // Powersaver chain
+ final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+ isRestrictedForPowerSave.put(FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForPowerSave.put(FIREWALL_RULE_ALLOW, false);
+ isRestrictedForPowerSave.put(FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+ // Standby chain
+ final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+ isRestrictedForStandby.put(FIREWALL_RULE_DEFAULT, false);
+ isRestrictedForStandby.put(FIREWALL_RULE_ALLOW, false);
+ isRestrictedForStandby.put(FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+
+ final int[] chains = {
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_DOZABLE
+ };
+ final int[] states = {
+ FIREWALL_RULE_ALLOW,
+ FIREWALL_RULE_DENY,
+ FIREWALL_RULE_DEFAULT
+ };
+ BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+ return String.format("Unexpected value for chain: %s and state: %s",
+ valueToString(NetworkPolicyManager.class, "FIREWALL_CHAIN_", chain),
+ valueToString(NetworkPolicyManager.class, "FIREWALL_RULE_", state));
+ };
+ for (int chain : chains) {
+ final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+ mInjector.setFirewallChainState(chain, true);
+ for (int state : states) {
+ mInjector.setFirewallRule(chain, TEST_UID, state);
+ assertEquals(errorMsg.apply(chain, state),
+ expectedValues.get(state), mNmi.isNetworkRestrictedForUid(TEST_UID));
+ }
+ mInjector.reset();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index cc5764b..b12da34 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -132,6 +132,7 @@
mHandler = new TestHandler(mHandlerThread.getLooper());
mInjector = new TestInjector();
mAms = new ActivityManagerService(mInjector);
+ mAms.mWaitForNetworkTimeoutMs = 100;
}
@After
@@ -217,6 +218,17 @@
44, // exptectedCurProcStateSeq
-1, // expectedBlockState, -1 to verify there are no interactions with main thread.
false); // expectNotify
+
+ // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented.
+ mAms.mWaitForNetworkTimeoutMs = 0;
+ mInjector.setNetworkRestrictedForUid(true);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_TOP, // prevState
+ PROCESS_STATE_IMPORTANT_BACKGROUND, // curState
+ 44, // expectedGlobalCounter
+ 44, // exptectedCurProcStateSeq
+ -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+ false); // expectNotify
}
private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
diff --git a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
index 4d5f783..a4e3988 100644
--- a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
@@ -52,6 +52,8 @@
import java.util.Collections;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -76,6 +78,12 @@
(int) VIBRATOR_PATTERN_2};
private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2};
+ // Convenience values for enabling/disabling to make code more readable
+ private static final int DISABLED = 0;
+ private static final int ENABLED_EXCEPT_LOCK_SCREEN = 1;
+ private static final int ENABLED_INCLUDING_LOCK_SCREEN = 2;
+ private static final int DISABLED_BUT_LOCK_SCREEN_ON = 3;
+
private @Mock Context mContext;
private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
private @Mock IAccessibilityManager mAccessibilityManagerService;
@@ -158,38 +166,103 @@
}
@Test
- public void testShortcutAvailable_withNullServiceIdWhenCreated_shouldReturnFalse() {
- configureShortcutDisabled();
- assertFalse(getController().isAccessibilityShortcutAvailable());
+ public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse() {
+ configureNoShortcutService();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ assertFalse(getController().isAccessibilityShortcutAvailable(false));
}
@Test
- public void testShortcutAvailable_withNonNullServiceIdWhenCreated_shouldReturnTrue() {
- configureShortcutEnabled();
- assertTrue(getController().isAccessibilityShortcutAvailable());
+ public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue() {
+ configureValidShortcutService();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ assertTrue(getController().isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse() {
+ configureValidShortcutService();
+ configureShortcutEnabled(DISABLED_BUT_LOCK_SCREEN_ON);
+ assertFalse(getController().isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse() {
+ configureValidShortcutService();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ assertFalse(getController().isAccessibilityShortcutAvailable(true));
+ }
+
+ @Test
+ public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue() {
+ configureValidShortcutService();
+ configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+ assertTrue(getController().isAccessibilityShortcutAvailable(true));
}
@Test
public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
accessibilityShortcutController.onSettingsChanged();
- assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+ assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
}
@Test
public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
- configureShortcutDisabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureNoShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
- configureShortcutEnabled();
+ configureValidShortcutService();
accessibilityShortcutController.onSettingsChanged();
- assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+ assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse() {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(DISABLED);
+ accessibilityShortcutController.onSettingsChanged();
+ assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue() {
+ configureShortcutEnabled(DISABLED);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ accessibilityShortcutController.onSettingsChanged();
+ assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse() {
+ configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ accessibilityShortcutController.onSettingsChanged();
+ assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
+ }
+
+ @Test
+ public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue() {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+ accessibilityShortcutController.onSettingsChanged();
+ assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
}
@Test
public void testOnAccessibilityShortcut_vibrates() {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
AccessibilityShortcutController accessibilityShortcutController = getController();
accessibilityShortcutController.performAccessibilityShortcut();
verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
@@ -198,7 +271,8 @@
@Test
public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
accessibilityShortcutController.performAccessibilityShortcut();
@@ -214,7 +288,8 @@
@Test
public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
accessibilityShortcutController.performAccessibilityShortcut();
@@ -229,7 +304,8 @@
@Test
public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
accessibilityShortcutController.performAccessibilityShortcut();
@@ -245,7 +321,8 @@
@Test
public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
getController().performAccessibilityShortcut();
@@ -261,7 +338,8 @@
@Test
public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
getController().performAccessibilityShortcut();
@@ -281,7 +359,8 @@
@Test
public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
- configureShortcutEnabled();
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
getController().performAccessibilityShortcut();
@@ -290,18 +369,48 @@
verify(mAccessibilityManagerService).performAccessibilityShortcut();
}
- private void configureShortcutDisabled() {
+ private void configureNoShortcutService() {
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
}
- private void configureShortcutEnabled() {
+ private void configureValidShortcutService() {
Settings.Secure.putString(
mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
}
+ private void configureShortcutEnabled(int enabledValue) {
+ final boolean enabled;
+ final boolean lockscreen;
+
+ switch (enabledValue) {
+ case DISABLED:
+ enabled = false;
+ lockscreen = false;
+ break;
+ case DISABLED_BUT_LOCK_SCREEN_ON:
+ enabled = false;
+ lockscreen = true;
+ break;
+ case ENABLED_INCLUDING_LOCK_SCREEN:
+ enabled = true;
+ lockscreen = true;
+ break;
+ case ENABLED_EXCEPT_LOCK_SCREEN:
+ enabled = true;
+ lockscreen = false;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_ENABLED, enabled ? 1 : 0);
+ Settings.Secure.putInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, lockscreen ? 1 : 0);
+ }
+
private AccessibilityShortcutController getController() {
AccessibilityShortcutController accessibilityShortcutController =
- new AccessibilityShortcutController(mContext, mHandler);
+ new AccessibilityShortcutController(mContext, mHandler, 0);
accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
return accessibilityShortcutController;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 74557e2..80b2e7d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -361,6 +361,18 @@
}
@Test
+ public void testOverrideConfigurationAncestorNotification() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer grandparent = builder.setLayer(0).build();
+
+ final TestWindowContainer parent = grandparent.addChildWindow();
+ final TestWindowContainer child = parent.addChildWindow();
+ child.onOverrideConfigurationChanged(new Configuration());
+
+ assertTrue(grandparent.mOnDescendantOverrideCalled);
+ }
+
+ @Test
public void testRemoveChild() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();
@@ -718,6 +730,7 @@
private Integer mOrientation;
private boolean mOnParentSetCalled;
+ private boolean mOnDescendantOverrideCalled;
/**
* Compares 2 window layers and returns -1 if the first is lesser than the second in terms
@@ -776,6 +789,12 @@
}
@Override
+ void onDescendantOverrideConfigurationChanged() {
+ mOnDescendantOverrideCalled = true;
+ super.onDescendantOverrideConfigurationChanged();
+ }
+
+ @Override
boolean isAnimating() {
return mIsAnimating || super.isAnimating();
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 28b6e45..1b1984d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -23,7 +23,6 @@
import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.graphics.Rect;
-import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -34,7 +33,6 @@
import android.view.WindowManager;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.FILL_PARENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -47,9 +45,8 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class WindowFrameTests {
+public class WindowFrameTests extends WindowTestsBase {
- private static WindowManagerService sWm = null;
private WindowToken mWindowToken;
private final IWindow mIWindow = new TestIWindow();
@@ -105,8 +102,7 @@
// Just any non zero value.
sWm.mSystemDecorLayer = 10000;
- mWindowToken = new WindowToken(sWm, new Binder(), 0, false,
- sWm.getDefaultDisplayContentLocked(), false /* ownerCanManageAppTokens */);
+ mWindowToken = new TestAppWindowToken(sWm.getDefaultDisplayContentLocked());
mStubStack = new TaskStack(sWm, 0);
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 6f78245..48799d2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -303,17 +303,19 @@
static class TestAppWindowToken extends AppWindowToken {
TestAppWindowToken(DisplayContent dc) {
- super(sWm, null, false, dc, true /* fillsParent */);
+ super(sWm, null, false, dc, true /* fillsParent */, null /* overrideConfig */,
+ null /* bounds */);
}
TestAppWindowToken(WindowManagerService service, IApplicationToken token,
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller) {
+ boolean alwaysFocusable, AppWindowContainerController controller,
+ Configuration overrideConfig, Rect bounds) {
super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen,
showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges,
- launchTaskBehind, alwaysFocusable, controller);
+ launchTaskBehind, alwaysFocusable, controller, overrideConfig, bounds);
}
int getWindowsCount() {
@@ -428,7 +430,8 @@
true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */,
false /* launchTaskBehind */, false /* alwaysFocusable */,
0 /* targetSdkVersion */, 0 /* rotationAnimationHint */,
- 0 /* inputDispatchingTimeoutNanos */, sWm);
+ 0 /* inputDispatchingTimeoutNanos */, sWm, null /* overrideConfig */,
+ null /* bounds */);
mToken = token;
}
@@ -437,12 +440,13 @@
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller) {
+ boolean alwaysFocusable, AppWindowContainerController controller,
+ Configuration overrideConfig, Rect bounds) {
return new TestAppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk,
orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
- controller);
+ controller, overrideConfig, bounds);
}
AppWindowToken getAppWindowToken() {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 1b28db7..e55e073 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -20,6 +20,7 @@
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.Build;
+import android.os.SystemProperties;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.TimeUtils;
@@ -56,6 +57,9 @@
private static final boolean DEBUG = UsageStatsService.DEBUG;
private static final String BAK_SUFFIX = ".bak";
private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX;
+ private static final String RETENTION_LEN_KEY = "ro.usagestats.chooser.retention";
+ private static final int SELECTION_LOG_RETENTION_LEN =
+ SystemProperties.getInt(RETENTION_LEN_KEY, 14);
private final Object mLock = new Object();
private final File[] mIntervalDirs;
@@ -504,7 +508,7 @@
mCal.getTimeInMillis());
mCal.setTimeInMillis(currentTimeMillis);
- mCal.addDays(-14);
+ mCal.addDays(-SELECTION_LOG_RETENTION_LEN);
for (int i = 0; i < mIntervalDirs.length; ++i) {
pruneChooserCountsOlderThan(mIntervalDirs[i], mCal.getTimeInMillis());
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 4da2853..f1cf441 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -99,6 +99,11 @@
*/
private static final String USB_STATE_PROPERTY = "sys.usb.state";
+ /**
+ * ro.bootmode value when phone boots into usual Android.
+ */
+ private static final String NORMAL_BOOT = "normal";
+
private static final String USB_STATE_MATCH =
"DEVPATH=/devices/virtual/android_usb/android0";
private static final String ACCESSORY_START_MATCH =
@@ -157,7 +162,7 @@
private boolean mMidiEnabled;
private int mMidiCard;
private int mMidiDevice;
- private Map<String, List<Pair<String, String>>> mOemModeMap;
+ private HashMap<String, HashMap<String, Pair<String, String>>> mOemModeMap;
private String[] mAccessoryStrings;
private UsbDebuggingManager mDebuggingManager;
private final UsbAlsaManager mUsbAlsaManager;
@@ -374,16 +379,32 @@
private boolean mAdbNotificationShown;
private int mCurrentUser = UserHandle.USER_NULL;
private boolean mUsbCharging;
+ private String mCurrentOemFunctions;
public UsbHandler(Looper looper) {
super(looper);
try {
// Restore default functions.
- mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
- UsbManager.USB_FUNCTION_NONE);
- mCurrentFunctionsApplied = mCurrentFunctions.equals(
- SystemProperties.get(USB_STATE_PROPERTY));
- mAdbEnabled = UsbManager.containsFunction(getDefaultFunctions(),
+
+ if (isNormalBoot()) {
+ mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
+ UsbManager.USB_FUNCTION_NONE);
+ mCurrentFunctionsApplied = mCurrentFunctions.equals(
+ SystemProperties.get(USB_STATE_PROPERTY));
+ } else {
+ mCurrentFunctions = SystemProperties.get(getPersistProp(true),
+ UsbManager.USB_FUNCTION_NONE);
+ mCurrentFunctionsApplied = SystemProperties.get(USB_CONFIG_PROPERTY,
+ UsbManager.USB_FUNCTION_NONE).equals(
+ SystemProperties.get(USB_STATE_PROPERTY));
+ }
+
+ /**
+ * Use the normal bootmode persistent prop to maintain state of adb across
+ * all boot modes.
+ */
+ mAdbEnabled = UsbManager.containsFunction(
+ SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY),
UsbManager.USB_FUNCTION_ADB);
/**
@@ -577,18 +598,36 @@
Slog.e(TAG, "Unable to set any USB functions!");
}
+ private boolean isNormalBoot() {
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ if (bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown")) {
+ return true;
+ }
+ return false;
+ }
+
private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
if (functions == null || applyAdbFunction(functions)
.equals(UsbManager.USB_FUNCTION_NONE)) {
functions = getDefaultFunctions();
}
functions = applyAdbFunction(functions);
- functions = applyOemOverrideFunction(functions);
- if (!mCurrentFunctions.equals(functions) || !mCurrentFunctionsApplied
+ String oemFunctions = applyOemOverrideFunction(functions);
+
+ if (!isNormalBoot() && !mCurrentFunctions.equals(functions)) {
+ SystemProperties.set(getPersistProp(true), functions);
+ }
+
+ if ((!functions.equals(oemFunctions) &&
+ (mCurrentOemFunctions == null ||
+ !mCurrentOemFunctions.equals(oemFunctions)))
+ || !mCurrentFunctions.equals(functions)
+ || !mCurrentFunctionsApplied
|| forceRestart) {
Slog.i(TAG, "Setting USB config to " + functions);
mCurrentFunctions = functions;
+ mCurrentOemFunctions = oemFunctions;
mCurrentFunctionsApplied = false;
// Kick the USB stack to close existing connections.
@@ -600,12 +639,12 @@
}
// Set the new USB configuration.
- setUsbConfig(functions);
+ setUsbConfig(oemFunctions);
// Start up dependent services.
updateUsbStateBroadcastIfNeeded(true);
- if (!waitForState(functions)) {
+ if (!waitForState(oemFunctions)) {
Slog.e(TAG, "Failed to switch USB config to " + functions);
return false;
}
@@ -616,6 +655,11 @@
}
private String applyAdbFunction(String functions) {
+ // Do not pass null pointer to the UsbManager.
+ // There isnt a check there.
+ if (functions == null) {
+ functions = "";
+ }
if (mAdbEnabled) {
functions = UsbManager.addFunction(functions, UsbManager.USB_FUNCTION_ADB);
} else {
@@ -1010,7 +1054,7 @@
}
private String getDefaultFunctions() {
- String func = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY,
+ String func = SystemProperties.get(getPersistProp(true),
UsbManager.USB_FUNCTION_NONE);
if (UsbManager.USB_FUNCTION_NONE.equals(func)) {
func = UsbManager.USB_FUNCTION_MTP;
@@ -1021,6 +1065,7 @@
public void dump(IndentingPrintWriter pw) {
pw.println("USB Device State:");
pw.println(" mCurrentFunctions: " + mCurrentFunctions);
+ pw.println(" mCurrentOemFunctions: " + mCurrentOemFunctions);
pw.println(" mCurrentFunctionsApplied: " + mCurrentFunctionsApplied);
pw.println(" mConnected: " + mConnected);
pw.println(" mConfigured: " + mConfigured);
@@ -1082,39 +1127,99 @@
if (configList != null) {
for (String config : configList) {
String[] items = config.split(":");
- if (items.length == 3) {
+ if (items.length == 3 || items.length == 4) {
if (mOemModeMap == null) {
- mOemModeMap = new HashMap<String, List<Pair<String, String>>>();
+ mOemModeMap = new HashMap<String, HashMap<String,
+ Pair<String, String>>>();
}
- List<Pair<String, String>> overrideList = mOemModeMap.get(items[0]);
- if (overrideList == null) {
- overrideList = new LinkedList<Pair<String, String>>();
- mOemModeMap.put(items[0], overrideList);
+ HashMap<String, Pair<String, String>> overrideMap
+ = mOemModeMap.get(items[0]);
+ if (overrideMap == null) {
+ overrideMap = new HashMap<String,
+ Pair<String, String>>();
+ mOemModeMap.put(items[0], overrideMap);
}
- overrideList.add(new Pair<String, String>(items[1], items[2]));
+
+ // Favoring the first combination if duplicate exists
+ if (!overrideMap.containsKey(items[1])) {
+ if (items.length == 3) {
+ overrideMap.put(items[1],
+ new Pair<String, String>(items[2], ""));
+ } else {
+ overrideMap.put(items[1],
+ new Pair<String, String>(items[2], items[3]));
+ }
+ }
}
}
}
}
private String applyOemOverrideFunction(String usbFunctions) {
- if ((usbFunctions == null) || (mOemModeMap == null)) return usbFunctions;
+ if ((usbFunctions == null) || (mOemModeMap == null)) {
+ return usbFunctions;
+ }
String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode);
- List<Pair<String, String>> overrides = mOemModeMap.get(bootMode);
- if (overrides != null) {
- for (Pair<String, String> pair : overrides) {
- if (pair.first.equals(usbFunctions)) {
- Slog.d(TAG, "OEM USB override: " + pair.first + " ==> " + pair.second);
- return pair.second;
+ Map<String, Pair<String, String>> overridesMap =
+ mOemModeMap.get(bootMode);
+ // Check to ensure that the oem is not overriding in the normal
+ // boot mode
+ if (overridesMap != null && !(bootMode.equals(NORMAL_BOOT) ||
+ bootMode.equals("unknown"))) {
+ Pair<String, String> overrideFunctions =
+ overridesMap.get(usbFunctions);
+ if (overrideFunctions != null) {
+ Slog.d(TAG, "OEM USB override: " + usbFunctions
+ + " ==> " + overrideFunctions.first
+ + " persist across reboot "
+ + overrideFunctions.second);
+ if (!overrideFunctions.second.equals("")) {
+ String newFunction;
+ if (mAdbEnabled) {
+ newFunction = UsbManager.addFunction(overrideFunctions.second,
+ UsbManager.USB_FUNCTION_ADB);
+ } else {
+ newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+ UsbManager.USB_FUNCTION_ADB);
+ }
+ Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
+ + UsbDeviceManager.getPersistProp(false));
+ SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+ newFunction);
}
+ return overrideFunctions.first;
+ } else if (mAdbEnabled) {
+ String newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+ UsbManager.USB_FUNCTION_ADB);
+ SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+ newFunction);
+ } else {
+ SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+ UsbManager.USB_FUNCTION_NONE);
}
}
// return passed in functions as is.
return usbFunctions;
}
+ public static String getPersistProp(boolean functions) {
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ String persistProp = USB_PERSISTENT_CONFIG_PROPERTY;
+ if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) {
+ if (functions == true) {
+ persistProp = "persist.sys.usb." + bootMode + ".func";
+ } else {
+ persistProp = "persist.sys.usb." + bootMode + ".config";
+ }
+ }
+
+ return persistProp;
+ }
+
+
public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
if (mDebuggingManager != null) {
mDebuggingManager.allowUsbDebugging(alwaysAllow, publicKey);
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 57036aa..ef3797c 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -87,6 +87,7 @@
"flatten/Archive.cpp",
"flatten/TableFlattener.cpp",
"flatten/XmlFlattener.cpp",
+ "io/BigBufferStreams.cpp",
"io/File.cpp",
"io/FileSystem.cpp",
"io/Io.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 1d04b35..b855f8f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -20,6 +20,7 @@
#include "ValueVisitor.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
+#include "io/BigBufferInputStream.h"
namespace aapt {
@@ -27,8 +28,7 @@
const android::StringPiece& path) {
Source source(path);
std::string error;
- std::unique_ptr<io::ZipFileCollection> apk =
- io::ZipFileCollection::Create(path, &error);
+ std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
if (!apk) {
context->GetDiagnostics()->Error(DiagMessage(source) << error);
return {};
@@ -36,21 +36,18 @@
io::IFile* file = apk->FindFile("resources.arsc");
if (!file) {
- context->GetDiagnostics()->Error(DiagMessage(source)
- << "no resources.arsc found");
+ context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
return {};
}
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
- context->GetDiagnostics()->Error(DiagMessage(source)
- << "could not open resources.arsc");
+ context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
return {};
}
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(context, table.get(), source, data->data(),
- data->size());
+ BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
if (!parser.Parse()) {
return {};
}
@@ -92,9 +89,9 @@
continue;
}
- // The resource table needs to be reserialized since it might have changed.
+ // The resource table needs to be re-serialized since it might have changed.
if (path == "resources.arsc") {
- BigBuffer buffer = BigBuffer(1024);
+ BigBuffer buffer(4096);
// TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
// with sparse entries) b/35389232.
TableFlattener flattener(options, &buffer);
@@ -102,8 +99,8 @@
return false;
}
- if (!writer->StartEntry(path, ArchiveEntry::kAlign) || !writer->WriteEntry(buffer) ||
- !writer->FinishEntry()) {
+ io::BigBufferInputStream input_stream(&buffer);
+ if (!writer->WriteFile(path, ArchiveEntry::kAlign, &input_stream)) {
context->GetDiagnostics()->Error(DiagMessage()
<< "Error when writing file '" << path << "' in APK.");
return false;
@@ -113,14 +110,12 @@
std::unique_ptr<io::IData> data = file->OpenAsData();
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
- if (!writer->StartEntry(path, compression_flags) ||
- !writer->WriteEntry(data->data(), data->size()) || !writer->FinishEntry()) {
+ if (!writer->WriteFile(path, compression_flags, data.get())) {
context->GetDiagnostics()->Error(DiagMessage()
<< "Error when writing file '" << path << "' in APK.");
return false;
}
}
-
return true;
}
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 456f686..5e9b81a 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
static const char* sMajorVersion = "2";
// Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "10";
+static const char* sMinorVersion = "11";
int PrintVersion() {
std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 8027f42..1fe30f0 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -37,6 +37,7 @@
#include "compile/XmlIdCollector.h"
#include "flatten/Archive.h"
#include "flatten/XmlFlattener.h"
+#include "io/BigBufferOutputStream.h"
#include "proto/ProtoSerialize.h"
#include "util/Files.h"
#include "util/Maybe.h"
@@ -46,7 +47,6 @@
using android::StringPiece;
using google::protobuf::io::CopyingOutputStreamAdaptor;
-using google::protobuf::io::ZeroCopyOutputStream;
namespace aapt {
@@ -142,10 +142,10 @@
IAaptContext* context, const CompileOptions& options,
std::vector<ResourcePathData>* out_path_data) {
const std::string& root_dir = options.res_dir.value();
- std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()),
- closedir);
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
if (!d) {
- context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+ context->GetDiagnostics()->Error(DiagMessage()
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -161,10 +161,10 @@
continue;
}
- std::unique_ptr<DIR, decltype(closedir)*> subdir(
- opendir(prefix_path.data()), closedir);
+ std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
if (!subdir) {
- context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+ context->GetDiagnostics()->Error(DiagMessage()
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -177,8 +177,7 @@
file::AppendPath(&full_path, leaf_entry->d_name);
std::string err_str;
- Maybe<ResourcePathData> path_data =
- ExtractResourcePathData(full_path, &err_str);
+ Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
if (!path_data) {
context->GetDiagnostics()->Error(DiagMessage() << err_str);
return false;
@@ -199,7 +198,7 @@
std::ifstream fin(path_data.source.path, std::ifstream::binary);
if (!fin) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
- << strerror(errno));
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -249,8 +248,7 @@
// Create the file/zip entry.
if (!writer->StartEntry(output_path, 0)) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to open");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
return false;
}
@@ -258,21 +256,18 @@
// writer->FinishEntry().
{
// Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream
- // interface.
+ // ZeroCopyOutputStream interface.
CopyingOutputStreamAdaptor copying_adaptor(writer);
std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
if (!pb_table->SerializeToZeroCopyStream(©ing_adaptor)) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to write");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
return false;
}
}
if (!writer->FinishEntry()) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to finish entry");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
return false;
}
return true;
@@ -293,16 +288,14 @@
// writer->FinishEntry().
{
// Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream
- // interface.
+ // ZeroCopyOutputStream interface.
CopyingOutputStreamAdaptor copying_adaptor(writer);
CompiledFileOutputStream output_stream(©ing_adaptor);
// Number of CompiledFiles.
output_stream.WriteLittleEndian32(1);
- std::unique_ptr<pb::CompiledFile> compiled_file =
- SerializeCompiledFileToPb(file);
+ std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
output_stream.WriteCompiledFile(compiled_file.get());
output_stream.WriteData(&buffer);
@@ -371,14 +364,12 @@
return false;
}
- std::unique_ptr<pb::CompiledFile> pb_compiled_file =
- SerializeCompiledFileToPb(xmlres->file);
+ std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file);
out->WriteCompiledFile(pb_compiled_file.get());
out->WriteData(&buffer);
if (out->HadError()) {
- context->GetDiagnostics()->Error(DiagMessage(output_path)
- << "failed to write data");
+ context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data");
return false;
}
return true;
@@ -388,8 +379,7 @@
const ResourcePathData& path_data,
IArchiveWriter* writer, const std::string& output_path) {
if (context->IsVerbose()) {
- context->GetDiagnostics()->Note(DiagMessage(path_data.source)
- << "compiling XML");
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
}
std::unique_ptr<xml::XmlResource> xmlres;
@@ -397,7 +387,7 @@
std::ifstream fin(path_data.source.path, std::ifstream::binary);
if (!fin) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
- << strerror(errno));
+ << android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -470,31 +460,6 @@
return true;
}
-class BigBufferOutputStream : public io::OutputStream {
- public:
- explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
-
- bool Next(void** data, int* len) override {
- size_t count;
- *data = buffer_->NextBlock(&count);
- *len = static_cast<int>(count);
- return true;
- }
-
- void BackUp(int count) override { buffer_->BackUp(count); }
-
- google::protobuf::int64 ByteCount() const override {
- return buffer_->size();
- }
-
- bool HadError() const override { return false; }
-
- private:
- BigBuffer* buffer_;
-
- DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
-};
-
static bool CompilePng(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data,
IArchiveWriter* writer, const std::string& output_path) {
@@ -520,7 +485,7 @@
}
BigBuffer crunched_png_buffer(4096);
- BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
+ io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
// Ensure that we only keep the chunks we care about if we end up
// using the original PNG instead of the crunched one.
@@ -533,8 +498,7 @@
std::unique_ptr<NinePatch> nine_patch;
if (path_data.extension == "9.png") {
std::string err;
- nine_patch = NinePatch::Create(image->rows.get(), image->width,
- image->height, &err);
+ nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
if (!nine_patch) {
context->GetDiagnostics()->Error(DiagMessage() << err);
return false;
@@ -547,8 +511,7 @@
// width - 2.
image->width -= 2;
image->height -= 2;
- memmove(image->rows.get(), image->rows.get() + 1,
- image->height * sizeof(uint8_t**));
+ memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
for (int32_t h = 0; h < image->height; h++) {
memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
}
@@ -560,8 +523,7 @@
}
// Write the crunched PNG.
- if (!WritePng(context, image.get(), nine_patch.get(),
- &crunched_png_buffer_out, {})) {
+ if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
return false;
}
@@ -574,24 +536,21 @@
// The re-encoded PNG is larger than the original, and there is
// no mandatory transformation. Use the original.
if (context->IsVerbose()) {
- context->GetDiagnostics()->Note(
- DiagMessage(path_data.source)
- << "original PNG is smaller than crunched PNG"
- << ", using original");
+ context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+ << "original PNG is smaller than crunched PNG"
+ << ", using original");
}
- PngChunkFilter png_chunk_filter_again(content);
+ png_chunk_filter.Rewind();
BigBuffer filtered_png_buffer(4096);
- BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
- io::Copy(&filtered_png_buffer_out, &png_chunk_filter_again);
+ io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
+ io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
buffer.AppendBuffer(std::move(filtered_png_buffer));
}
if (context->IsVerbose()) {
- // For debugging only, use the legacy PNG cruncher and compare the
- // resulting file sizes.
- // This will help catch exotic cases where the new code may generate
- // larger PNGs.
+ // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
+ // This will help catch exotic cases where the new code may generate larger PNGs.
std::stringstream legacy_stream(content);
BigBuffer legacy_buffer(4096);
Png png(context->GetDiagnostics());
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 5e15c88..6d6147d 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -33,7 +33,6 @@
namespace aapt {
constexpr bool kDebug = false;
-constexpr size_t kPngSignatureSize = 8u;
struct PngInfo {
~PngInfo() {
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index a820051..e4255e7 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -31,6 +31,9 @@
namespace aapt {
+// Size in bytes of the PNG signature.
+constexpr size_t kPngSignatureSize = 8u;
+
struct PngOptions {
int grayscale_tolerance = 0;
};
@@ -46,9 +49,9 @@
const PngOptions& options);
private:
- IDiagnostics* mDiag;
-
DISALLOW_COPY_AND_ASSIGN(Png);
+
+ IDiagnostics* mDiag;
};
/**
@@ -57,26 +60,26 @@
class PngChunkFilter : public io::InputStream {
public:
explicit PngChunkFilter(const android::StringPiece& data);
+ virtual ~PngChunkFilter() = default;
- bool Next(const void** buffer, int* len) override;
- void BackUp(int count) override;
- bool Skip(int count) override;
+ bool Next(const void** buffer, size_t* len) override;
+ void BackUp(size_t count) override;
- google::protobuf::int64 ByteCount() const override {
- return static_cast<google::protobuf::int64>(window_start_);
- }
+ bool CanRewind() const override { return true; }
+ bool Rewind() override;
+ size_t ByteCount() const override { return window_start_; }
bool HadError() const override { return error_; }
private:
- bool ConsumeWindow(const void** buffer, int* len);
+ DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+
+ bool ConsumeWindow(const void** buffer, size_t* len);
android::StringPiece data_;
size_t window_start_ = 0;
size_t window_end_ = 0;
bool error_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
};
/**
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index edec123..f9043b5 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -71,16 +71,16 @@
PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
if (util::StartsWith(data_, kPngSignature)) {
window_start_ = 0;
- window_end_ = strlen(kPngSignature);
+ window_end_ = kPngSignatureSize;
} else {
error_ = true;
}
}
-bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) {
+bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) {
if (window_start_ != window_end_) {
// We have bytes to give from our window.
- const int bytes_read = (int)(window_end_ - window_start_);
+ const size_t bytes_read = window_end_ - window_start_;
*buffer = data_.data() + window_start_;
*len = bytes_read;
window_start_ = window_end_;
@@ -89,7 +89,7 @@
return false;
}
-bool PngChunkFilter::Next(const void** buffer, int* len) {
+bool PngChunkFilter::Next(const void** buffer, size_t* len) {
if (error_) {
return false;
}
@@ -113,16 +113,14 @@
// Verify the chunk length.
const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
- if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) >
- data_.size()) {
+ if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) > data_.size()) {
// Overflow.
error_ = true;
return false;
}
// Do we strip this chunk?
- const uint32_t chunk_type =
- Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+ const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
if (IsPngChunkWhitelisted(chunk_type)) {
// Advance the window to include this chunk.
window_end_ += kMinChunkHeaderSize + chunk_len;
@@ -146,31 +144,19 @@
return false;
}
-void PngChunkFilter::BackUp(int count) {
+void PngChunkFilter::BackUp(size_t count) {
if (error_) {
return;
}
window_start_ -= count;
}
-bool PngChunkFilter::Skip(int count) {
+bool PngChunkFilter::Rewind() {
if (error_) {
return false;
}
-
- const void* buffer;
- int len;
- while (count > 0) {
- if (!Next(&buffer, &len)) {
- return false;
- }
- if (len > count) {
- BackUp(len - count);
- count = 0;
- } else {
- count -= len;
- }
- }
+ window_start_ = 0;
+ window_end_ = kPngSignatureSize;
return true;
}
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
index 3b46d8b..ae98afc 100644
--- a/tools/aapt2/compile/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -29,12 +29,7 @@
namespace aapt {
-// Size in bytes of the PNG signature.
-constexpr size_t kPngSignatureSize = 8u;
-
-/**
- * Custom deleter that destroys libpng read and info structs.
- */
+// Custom deleter that destroys libpng read and info structs.
class PngReadStructDeleter {
public:
PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
@@ -51,9 +46,7 @@
DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
};
-/**
- * Custom deleter that destroys libpng write and info structs.
- */
+// Custom deleter that destroys libpng write and info structs.
class PngWriteStructDeleter {
public:
PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
@@ -82,12 +75,11 @@
diag->Error(DiagMessage() << error_msg);
}
-static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer,
- png_size_t len) {
+static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
const void* in_buffer;
- int in_len;
+ size_t in_len;
if (!in->Next(&in_buffer, &in_len)) {
if (in->HadError()) {
std::string err = in->GetError();
@@ -96,19 +88,18 @@
return;
}
- const size_t bytes_read = std::min(static_cast<size_t>(in_len), len);
+ const size_t bytes_read = std::min(in_len, len);
memcpy(buffer, in_buffer, bytes_read);
- if (bytes_read != static_cast<size_t>(in_len)) {
- in->BackUp(in_len - static_cast<int>(bytes_read));
+ if (bytes_read != in_len) {
+ in->BackUp(in_len - bytes_read);
}
}
-static void WriteDataToStream(png_structp png_ptr, png_bytep buffer,
- png_size_t len) {
+static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
void* out_buffer;
- int out_len;
+ size_t out_len;
while (len > 0) {
if (!out->Next(&out_buffer, &out_len)) {
if (out->HadError()) {
@@ -118,7 +109,7 @@
return;
}
- const size_t bytes_written = std::min(static_cast<size_t>(out_len), len);
+ const size_t bytes_written = std::min(out_len, len);
memcpy(out_buffer, buffer, bytes_written);
// Advance the input buffer.
@@ -126,7 +117,7 @@
len -= bytes_written;
// Advance the output buffer.
- out_len -= static_cast<int>(bytes_written);
+ out_len -= bytes_written;
}
// If the entire output buffer wasn't used, backup.
@@ -139,41 +130,35 @@
// Read the first 8 bytes of the file looking for the PNG signature.
// Bail early if it does not match.
const png_byte* signature;
- int buffer_size;
+ size_t buffer_size;
if (!in->Next((const void**)&signature, &buffer_size)) {
- context->GetDiagnostics()->Error(
- DiagMessage() << android::base::SystemErrorCodeToString(errno));
+ context->GetDiagnostics()->Error(DiagMessage()
+ << android::base::SystemErrorCodeToString(errno));
return {};
}
- if (static_cast<size_t>(buffer_size) < kPngSignatureSize ||
- png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "file signature does not match PNG signature");
+ if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "file signature does not match PNG signature");
return {};
}
// Start at the beginning of the first chunk.
- in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize));
+ in->BackUp(buffer_size - kPngSignatureSize);
- // Create and initialize the png_struct with the default error and warning
- // handlers.
- // The header version is also passed in to ensure that this was built against
- // the same
+ // Create and initialize the png_struct with the default error and warning handlers.
+ // The header version is also passed in to ensure that this was built against the same
// version of libpng.
- png_structp read_ptr =
- png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (read_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng read png_struct");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_struct");
return {};
}
// Create and initialize the memory for image header and data.
png_infop info_ptr = png_create_info_struct(read_ptr);
if (info_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng read png_info");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_info");
png_destroy_read_struct(&read_ptr, nullptr, nullptr);
return {};
}
@@ -189,8 +174,7 @@
}
// Handle warnings ourselves via IDiagnostics.
- png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError,
- LogWarning);
+ png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
// Set up the read functions which read from our custom data sources.
png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
@@ -203,8 +187,7 @@
// Extract image meta-data from the various chunk headers.
uint32_t width, height;
- int bit_depth, color_type, interlace_method, compression_method,
- filter_method;
+ int bit_depth, color_type, interlace_method, compression_method, filter_method;
png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
&interlace_method, &compression_method, &filter_method);
@@ -247,11 +230,9 @@
// 9-patch uses int32_t to index images, so we cap the image dimensions to
// something
// that can always be represented by 9-patch.
- if (width > std::numeric_limits<int32_t>::max() ||
- height > std::numeric_limits<int32_t>::max()) {
- context->GetDiagnostics()->Error(DiagMessage()
- << "PNG image dimensions are too large: "
- << width << "x" << height);
+ if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
+ context->GetDiagnostics()->Error(
+ DiagMessage() << "PNG image dimensions are too large: " << width << "x" << height);
return {};
}
@@ -263,8 +244,7 @@
CHECK(row_bytes == 4 * width); // RGBA
// Allocate one large block to hold the image.
- output_image->data =
- std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
+ output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
// Create an array of rows that index into the data block.
output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
@@ -281,19 +261,13 @@
return output_image;
}
-/**
- * Experimentally chosen constant to be added to the overhead of using color
- * type
- * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
- * chunk.
- * Without this, many small PNGs encoded with palettes are larger after
- * compression than
- * the same PNGs encoded as RGBA.
- */
+// Experimentally chosen constant to be added to the overhead of using color type
+// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
+// Without this, many small PNGs encoded with palettes are larger after compression than
+// the same PNGs encoded as RGBA.
constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
-// Pick a color type by which to encode the image, based on which color type
-// will take
+// Pick a color type by which to encode the image, based on which color type will take
// the least amount of disk space.
//
// 9-patch images traditionally have not been encoded with palettes.
@@ -372,20 +346,17 @@
return PNG_COLOR_TYPE_RGBA;
}
-// Assigns indices to the color and alpha palettes, encodes them, and then
-// invokes
+// Assigns indices to the color and alpha palettes, encodes them, and then invokes
// png_set_PLTE/png_set_tRNS.
// This must be done before writing image data.
-// Image data must be transformed to use the indices assigned within the
-// palette.
+// Image data must be transformed to use the indices assigned within the palette.
static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
std::unordered_map<uint32_t, int>* color_palette,
std::unordered_set<uint32_t>* alpha_palette) {
CHECK(color_palette->size() <= 256);
CHECK(alpha_palette->size() <= 256);
- // Populate the PNG palette struct and assign indices to the color
- // palette.
+ // Populate the PNG palette struct and assign indices to the color palette.
// Colors in the alpha palette should have smaller indices.
// This will ensure that we can truncate the alpha palette if it is
@@ -403,13 +374,11 @@
}
// Create the PNG color palette struct.
- auto color_palette_bytes =
- std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
+ auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
std::unique_ptr<png_byte[]> alpha_palette_bytes;
if (!alpha_palette->empty()) {
- alpha_palette_bytes =
- std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
+ alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
}
for (const auto& entry : *color_palette) {
@@ -433,23 +402,20 @@
// The bytes get copied here, so it is safe to release color_palette_bytes at
// the end of function
// scope.
- png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(),
- color_palette->size());
+ png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size());
if (alpha_palette_bytes) {
- png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(),
- alpha_palette->size(), nullptr);
+ png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(),
+ nullptr);
}
}
// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
-// before
-// writing image data.
+// before writing image data.
static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
const NinePatch* nine_patch) {
// The order of the chunks is important.
- // 9-patch code in older platforms expects the 9-patch chunk to
- // be last.
+ // 9-patch code in older platforms expects the 9-patch chunk to be last.
png_unknown_chunk unknown_chunks[3];
memset(unknown_chunks, 0, sizeof(unknown_chunks));
@@ -475,8 +441,7 @@
index++;
}
- std::unique_ptr<uint8_t[]> serialized_nine_patch =
- nine_patch->SerializeBase(&chunk_len);
+ std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len);
strcpy((char*)unknown_chunks[index].name, "npTc");
unknown_chunks[index].size = chunk_len;
unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
@@ -497,22 +462,18 @@
const PngOptions& options) {
// Create and initialize the write png_struct with the default error and
// warning handlers.
- // The header version is also passed in to ensure that this was built against
- // the same
+ // The header version is also passed in to ensure that this was built against the same
// version of libpng.
- png_structp write_ptr =
- png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (write_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng write png_struct");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_struct");
return false;
}
// Allocate memory to store image header data.
png_infop write_info_ptr = png_create_info_struct(write_ptr);
if (write_info_ptr == nullptr) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "failed to create libpng write png_info");
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_info");
png_destroy_write_struct(&write_ptr, nullptr);
return false;
}
@@ -527,8 +488,7 @@
}
// Handle warnings with our IDiagnostics.
- png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError,
- LogWarning);
+ png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
// Set up the write functions which write to our custom data sources.
png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
@@ -599,8 +559,7 @@
context->GetDiagnostics()->Note(msg);
}
- const bool convertible_to_grayscale =
- max_gray_deviation <= options.grayscale_tolerance;
+ const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
const int new_color_type = PickColorType(
image->width, image->height, grayscale, convertible_to_grayscale,
@@ -715,15 +674,12 @@
}
png_write_row(write_ptr, out_row.get());
}
- } else if (new_color_type == PNG_COLOR_TYPE_RGB ||
- new_color_type == PNG_COLOR_TYPE_RGBA) {
+ } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) {
const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
if (needs_to_zero_rgb_channels_of_transparent_pixels) {
// The source RGBA data can't be used as-is, because we need to zero out
- // the RGB
- // values of transparent pixels.
- auto out_row =
- std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+ // the RGB values of transparent pixels.
+ auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
for (int32_t y = 0; y < image->height; y++) {
png_const_bytep in_row = image->rows[y];
@@ -747,8 +703,7 @@
}
} else {
// The source image can be used as-is, just tell libpng whether or not to
- // ignore
- // the alpha channel.
+ // ignore the alpha channel.
if (new_color_type == PNG_COLOR_TYPE_RGB) {
// Delete the extraneous alpha values that we appended to our buffer
// when reading the original values.
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 5c96a4d..826f91b 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -21,6 +21,7 @@
#include <string>
#include <vector>
+#include "android-base/errors.h"
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
#include "ziparchive/zip_writer.h"
@@ -37,14 +38,14 @@
public:
DirectoryWriter() = default;
- bool Open(IDiagnostics* diag, const StringPiece& out_dir) {
+ bool Open(const StringPiece& out_dir) {
dir_ = out_dir.to_string();
file::FileType type = file::GetFileType(dir_);
if (type == file::FileType::kNonexistant) {
- diag->Error(DiagMessage() << "directory " << dir_ << " does not exist");
+ error_ = "directory does not exist";
return false;
} else if (type != file::FileType::kDirectory) {
- diag->Error(DiagMessage() << dir_ << " is not a directory");
+ error_ = "not a directory";
return false;
}
return true;
@@ -61,27 +62,19 @@
file_ = {fopen(full_path.data(), "wb"), fclose};
if (!file_) {
+ error_ = android::base::SystemErrorCodeToString(errno);
return false;
}
return true;
}
- bool WriteEntry(const BigBuffer& buffer) override {
+ bool Write(const void* data, int len) override {
if (!file_) {
return false;
}
- for (const BigBuffer::Block& b : buffer) {
- if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) {
- file_.reset(nullptr);
- return false;
- }
- }
- return true;
- }
-
- bool WriteEntry(const void* data, size_t len) override {
- if (fwrite(data, 1, len, file_.get()) != len) {
+ if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) {
+ error_ = android::base::SystemErrorCodeToString(errno);
file_.reset(nullptr);
return false;
}
@@ -96,22 +89,41 @@
return true;
}
+ bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+ if (!StartEntry(path, flags)) {
+ return false;
+ }
+
+ const void* data = nullptr;
+ size_t len = 0;
+ while (in->Next(&data, &len)) {
+ if (!Write(data, static_cast<int>(len))) {
+ return false;
+ }
+ }
+ return !in->HadError();
+ }
+
+ bool HadError() const override { return !error_.empty(); }
+
+ std::string GetError() const override { return error_; }
+
private:
DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
std::string dir_;
std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+ std::string error_;
};
class ZipFileWriter : public IArchiveWriter {
public:
ZipFileWriter() = default;
- bool Open(IDiagnostics* diag, const StringPiece& path) {
+ bool Open(const StringPiece& path) {
file_ = {fopen(path.data(), "w+b"), fclose};
if (!file_) {
- diag->Error(DiagMessage() << "failed to Open " << path << ": "
- << strerror(errno));
+ error_ = android::base::SystemErrorCodeToString(errno);
return false;
}
writer_ = util::make_unique<ZipWriter>(file_.get());
@@ -134,37 +146,83 @@
int32_t result = writer_->StartEntry(path.data(), zip_flags);
if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
return false;
}
return true;
}
- bool WriteEntry(const void* data, size_t len) override {
+ bool Write(const void* data, int len) override {
int32_t result = writer_->WriteBytes(data, len);
if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
return false;
}
return true;
}
- bool WriteEntry(const BigBuffer& buffer) override {
- for (const BigBuffer::Block& b : buffer) {
- int32_t result = writer_->WriteBytes(b.buffer.get(), b.size);
- if (result != 0) {
- return false;
- }
- }
- return true;
- }
-
bool FinishEntry() override {
int32_t result = writer_->FinishEntry();
if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
return false;
}
return true;
}
+ bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+ while (true) {
+ if (!StartEntry(path, flags)) {
+ return false;
+ }
+
+ const void* data = nullptr;
+ size_t len = 0;
+ while (in->Next(&data, &len)) {
+ if (!Write(data, static_cast<int>(len))) {
+ return false;
+ }
+ }
+
+ if (in->HadError()) {
+ return false;
+ }
+
+ if (!FinishEntry()) {
+ return false;
+ }
+
+ // Check to see if the file was compressed enough. This is preserving behavior of AAPT.
+ if ((flags & ArchiveEntry::kCompress) != 0 && in->CanRewind()) {
+ ZipWriter::FileEntry last_entry;
+ int32_t result = writer_->GetLastEntry(&last_entry);
+ CHECK(result == 0);
+ if (last_entry.compressed_size + (last_entry.compressed_size / 10) >
+ last_entry.uncompressed_size) {
+ // The file was not compressed enough, rewind and store it uncompressed.
+ if (!in->Rewind()) {
+ // Well we tried, may as well keep what we had.
+ return true;
+ }
+
+ int32_t result = writer_->DiscardLastEntry();
+ if (result != 0) {
+ error_ = ZipWriter::ErrorCodeString(result);
+ return false;
+ }
+ flags &= ~ArchiveEntry::kCompress;
+
+ continue;
+ }
+ }
+ return true;
+ }
+ }
+
+ bool HadError() const override { return !error_.empty(); }
+
+ std::string GetError() const override { return error_; }
+
virtual ~ZipFileWriter() {
if (writer_) {
writer_->Finish();
@@ -176,24 +234,26 @@
std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
std::unique_ptr<ZipWriter> writer_;
+ std::string error_;
};
} // namespace
-std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(
- IDiagnostics* diag, const StringPiece& path) {
- std::unique_ptr<DirectoryWriter> writer =
- util::make_unique<DirectoryWriter>();
- if (!writer->Open(diag, path)) {
+std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+ std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+ if (!writer->Open(path)) {
+ diag->Error(DiagMessage(path) << writer->GetError());
return {};
}
return std::move(writer);
}
-std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(
- IDiagnostics* diag, const StringPiece& path) {
+std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
- if (!writer->Open(diag, path)) {
+ if (!writer->Open(path)) {
+ diag->Error(DiagMessage(path) << writer->GetError());
return {};
}
return std::move(writer);
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index f0681bd..4ee4ce7 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -26,6 +26,7 @@
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "Diagnostics.h"
+#include "io/Io.h"
#include "util/BigBuffer.h"
#include "util/Files.h"
@@ -42,19 +43,31 @@
size_t uncompressed_size;
};
-class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
+class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream {
public:
virtual ~IArchiveWriter() = default;
+ virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+
+ // Starts a new entry and allows caller to write bytes to it sequentially.
+ // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
+ // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
- virtual bool WriteEntry(const BigBuffer& buffer) = 0;
- virtual bool WriteEntry(const void* data, size_t len) = 0;
+
+ // Called to finish writing an entry previously started by StartEntry.
+ // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
virtual bool FinishEntry() = 0;
- // CopyingOutputStream implementations.
- bool Write(const void* buffer, int size) override {
- return WriteEntry(buffer, size);
- }
+ // CopyingOutputStream implementation that allows sequential writes to this archive. Only
+ // valid between calls to StartEntry and FinishEntry.
+ virtual bool Write(const void* buffer, int size) = 0;
+
+ // Returns true if there was an error writing to the archive.
+ // The resulting error message can be retrieved from GetError().
+ virtual bool HadError() const = 0;
+
+ // Returns the error message if HadError() returns true.
+ virtual std::string GetError() const = 0;
};
std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferInputStream.h
new file mode 100644
index 0000000..92612c7
--- /dev/null
+++ b/tools/aapt2/io/BigBufferInputStream.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H
+#define AAPT_IO_BIGBUFFERINPUTSTREAM_H
+
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+namespace io {
+
+class BigBufferInputStream : public InputStream {
+ public:
+ inline explicit BigBufferInputStream(const BigBuffer* buffer)
+ : buffer_(buffer), iter_(buffer->begin()) {}
+ virtual ~BigBufferInputStream() = default;
+
+ bool Next(const void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ bool CanRewind() const override;
+
+ bool Rewind() override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
+
+ const BigBuffer* buffer_;
+ BigBuffer::const_iterator iter_;
+ size_t offset_ = 0;
+ size_t bytes_read_ = 0;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_BIGBUFFERINPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h
new file mode 100644
index 0000000..95113bc
--- /dev/null
+++ b/tools/aapt2/io/BigBufferOutputStream.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
+#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
+
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+namespace io {
+
+class BigBufferOutputStream : public OutputStream {
+ public:
+ inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
+ virtual ~BigBufferOutputStream() = default;
+
+ bool Next(void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+ BigBuffer* buffer_;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStreams.cpp
new file mode 100644
index 0000000..eb99033
--- /dev/null
+++ b/tools/aapt2/io/BigBufferStreams.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "io/BigBufferInputStream.h"
+#include "io/BigBufferOutputStream.h"
+
+namespace aapt {
+namespace io {
+
+//
+// BigBufferInputStream
+//
+
+bool BigBufferInputStream::Next(const void** data, size_t* size) {
+ if (iter_ == buffer_->end()) {
+ return false;
+ }
+
+ if (offset_ == iter_->size) {
+ ++iter_;
+ if (iter_ == buffer_->end()) {
+ return false;
+ }
+ offset_ = 0;
+ }
+
+ *data = iter_->buffer.get() + offset_;
+ *size = iter_->size - offset_;
+ bytes_read_ += iter_->size - offset_;
+ offset_ = iter_->size;
+ return true;
+}
+
+void BigBufferInputStream::BackUp(size_t count) {
+ if (count > offset_) {
+ bytes_read_ -= offset_;
+ offset_ = 0;
+ } else {
+ offset_ -= count;
+ bytes_read_ -= count;
+ }
+}
+
+bool BigBufferInputStream::CanRewind() const { return true; }
+
+bool BigBufferInputStream::Rewind() {
+ iter_ = buffer_->begin();
+ offset_ = 0;
+ bytes_read_ = 0;
+ return true;
+}
+
+size_t BigBufferInputStream::ByteCount() const { return bytes_read_; }
+
+bool BigBufferInputStream::HadError() const { return false; }
+
+//
+// BigBufferOutputStream
+//
+
+bool BigBufferOutputStream::Next(void** data, size_t* size) {
+ *data = buffer_->NextBlock(size);
+ return true;
+}
+
+void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); }
+
+size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); }
+
+bool BigBufferOutputStream::HadError() const { return false; }
+
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index fdc044d..09dc7ea 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -22,14 +22,13 @@
#include "android-base/macros.h"
#include "utils/FileMap.h"
+#include "io/Io.h"
+
namespace aapt {
namespace io {
-/**
- * Interface for a block of contiguous memory. An instance of this interface
- * owns the data.
- */
-class IData {
+// Interface for a block of contiguous memory. An instance of this interface owns the data.
+class IData : public InputStream {
public:
virtual ~IData() = default;
@@ -40,7 +39,8 @@
class DataSegment : public IData {
public:
explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len)
- : data_(std::move(data)), offset_(offset), len_(len) {}
+ : data_(std::move(data)), offset_(offset), len_(len), next_read_(offset) {}
+ virtual ~DataSegment() = default;
const void* data() const override {
return static_cast<const uint8_t*>(data_->data()) + offset_;
@@ -48,63 +48,163 @@
size_t size() const override { return len_; }
+ bool Next(const void** data, size_t* size) override {
+ if (next_read_ == offset_ + len_) {
+ return false;
+ }
+ *data = static_cast<const uint8_t*>(data_->data()) + next_read_;
+ *size = len_ - (next_read_ - offset_);
+ next_read_ = offset_ + len_;
+ return true;
+ }
+
+ void BackUp(size_t count) override {
+ if (count > next_read_ - offset_) {
+ next_read_ = offset_;
+ } else {
+ next_read_ -= count;
+ }
+ }
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override {
+ next_read_ = offset_;
+ return true;
+ }
+
+ size_t ByteCount() const override { return next_read_ - offset_; }
+
+ bool HadError() const override { return false; }
+
private:
DISALLOW_COPY_AND_ASSIGN(DataSegment);
std::unique_ptr<IData> data_;
size_t offset_;
size_t len_;
+ size_t next_read_;
};
-/**
- * Implementation of IData that exposes a memory mapped file. The mmapped file
- * is owned by this
- * object.
- */
+// Implementation of IData that exposes a memory mapped file.
+// The mmapped file is owned by this object.
class MmappedData : public IData {
public:
- explicit MmappedData(android::FileMap&& map)
- : map_(std::forward<android::FileMap>(map)) {}
+ explicit MmappedData(android::FileMap&& map) : map_(std::forward<android::FileMap>(map)) {}
+ virtual ~MmappedData() = default;
const void* data() const override { return map_.getDataPtr(); }
size_t size() const override { return map_.getDataLength(); }
+ bool Next(const void** data, size_t* size) override {
+ if (next_read_ == map_.getDataLength()) {
+ return false;
+ }
+ *data = reinterpret_cast<const uint8_t*>(map_.getDataPtr()) + next_read_;
+ *size = map_.getDataLength() - next_read_;
+ next_read_ = map_.getDataLength();
+ return true;
+ }
+
+ void BackUp(size_t count) override {
+ if (count > next_read_) {
+ next_read_ = 0;
+ } else {
+ next_read_ -= count;
+ }
+ }
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override {
+ next_read_ = 0;
+ return true;
+ }
+
+ size_t ByteCount() const override { return next_read_; }
+
+ bool HadError() const override { return false; }
+
private:
+ DISALLOW_COPY_AND_ASSIGN(MmappedData);
+
android::FileMap map_;
+ size_t next_read_ = 0;
};
-/**
- * Implementation of IData that exposes a block of memory that was malloc'ed
- * (new'ed). The
- * memory is owned by this object.
- */
+// Implementation of IData that exposes a block of memory that was malloc'ed (new'ed).
+// The memory is owned by this object.
class MallocData : public IData {
public:
MallocData(std::unique_ptr<const uint8_t[]> data, size_t size)
: data_(std::move(data)), size_(size) {}
+ virtual ~MallocData() = default;
const void* data() const override { return data_.get(); }
size_t size() const override { return size_; }
+ bool Next(const void** data, size_t* size) override {
+ if (next_read_ == size_) {
+ return false;
+ }
+ *data = data_.get() + next_read_;
+ *size = size_ - next_read_;
+ next_read_ = size_;
+ return true;
+ }
+
+ void BackUp(size_t count) override {
+ if (count > next_read_) {
+ next_read_ = 0;
+ } else {
+ next_read_ -= count;
+ }
+ }
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override {
+ next_read_ = 0;
+ return true;
+ }
+
+ size_t ByteCount() const override { return next_read_; }
+
+ bool HadError() const override { return false; }
+
private:
+ DISALLOW_COPY_AND_ASSIGN(MallocData);
+
std::unique_ptr<const uint8_t[]> data_;
size_t size_;
+ size_t next_read_ = 0;
};
-/**
- * When mmap fails because the file has length 0, we use the EmptyData to
- * simulate data of length 0.
- */
+// When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
class EmptyData : public IData {
public:
+ virtual ~EmptyData() = default;
+
const void* data() const override {
static const uint8_t d = 0;
return &d;
}
size_t size() const override { return 0u; }
+
+ bool Next(const void** /*data*/, size_t* /*size*/) override { return false; }
+
+ void BackUp(size_t /*count*/) override {}
+
+ bool CanRewind() const override { return true; }
+
+ bool Rewind() override { return true; }
+
+ size_t ByteCount() const override { return 0u; }
+
+ bool HadError() const override { return false; }
};
} // namespace io
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 1ef9743..7ef6d88 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -30,40 +30,27 @@
namespace aapt {
namespace io {
-/**
- * Interface for a file, which could be a real file on the file system, or a
- * file inside
- * a ZIP archive.
- */
+// Interface for a file, which could be a real file on the file system, or a
+// file inside a ZIP archive.
class IFile {
public:
virtual ~IFile() = default;
- /**
- * Open the file and return it as a block of contiguous memory. How this
- * occurs is
- * implementation dependent. For example, if this is a file on the file
- * system, it may
- * simply mmap the contents. If this file represents a compressed file in a
- * ZIP archive,
- * it may need to inflate it to memory, incurring a copy.
- *
- * Returns nullptr on failure.
- */
+ // Open the file and return it as a block of contiguous memory. How this
+ // occurs is implementation dependent. For example, if this is a file on the file
+ // system, it may simply mmap the contents. If this file represents a compressed file in a
+ // ZIP archive, it may need to inflate it to memory, incurring a copy.
+ // Returns nullptr on failure.
virtual std::unique_ptr<IData> OpenAsData() = 0;
- /**
- * Returns the source of this file. This is for presentation to the user and
- * may not be a
- * valid file system path (for example, it may contain a '@' sign to separate
- * the files within
- * a ZIP archive from the path to the containing ZIP archive.
- */
+ // Returns the source of this file. This is for presentation to the user and
+ // may not be a valid file system path (for example, it may contain a '@' sign to separate
+ // the files within a ZIP archive from the path to the containing ZIP archive.
virtual const Source& GetSource() const = 0;
IFile* CreateFileSegment(size_t offset, size_t len);
- /** Returns whether the file was compressed before it was stored in memory. */
+ // Returns whether the file was compressed before it was stored in memory.
virtual bool WasCompressed() {
return false;
}
@@ -77,10 +64,7 @@
std::list<std::unique_ptr<IFile>> segments_;
};
-/**
- * An IFile that wraps an underlying IFile but limits it to a subsection of that
- * file.
- */
+// An IFile that wraps an underlying IFile but limits it to a subsection of that file.
class FileSegment : public IFile {
public:
explicit FileSegment(IFile* file, size_t offset, size_t len)
@@ -106,11 +90,8 @@
virtual IFile* Next() = 0;
};
-/**
- * Interface for a collection of files, all of which share a common source. That
- * source may
- * simply be the filesystem, or a ZIP archive.
- */
+// Interface for a collection of files, all of which share a common source. That source may
+// simply be the filesystem, or a ZIP archive.
class IFileCollection {
public:
virtual ~IFileCollection() = default;
diff --git a/tools/aapt2/io/Io.cpp b/tools/aapt2/io/Io.cpp
index cab4b65..f5c5737 100644
--- a/tools/aapt2/io/Io.cpp
+++ b/tools/aapt2/io/Io.cpp
@@ -16,7 +16,6 @@
#include "io/Io.h"
-#include <algorithm>
#include <cstring>
namespace aapt {
@@ -24,15 +23,15 @@
bool Copy(OutputStream* out, InputStream* in) {
const void* in_buffer;
- int in_len;
+ size_t in_len;
while (in->Next(&in_buffer, &in_len)) {
void* out_buffer;
- int out_len;
+ size_t out_len;
if (!out->Next(&out_buffer, &out_len)) {
return !out->HadError();
}
- const int bytes_to_copy = std::min(in_len, out_len);
+ const size_t bytes_to_copy = in_len < out_len ? in_len : out_len;
memcpy(out_buffer, in_buffer, bytes_to_copy);
out->BackUp(out_len - bytes_to_copy);
in->BackUp(in_len - bytes_to_copy);
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
index 33cdc7b..2a34d4d 100644
--- a/tools/aapt2/io/Io.h
+++ b/tools/aapt2/io/Io.h
@@ -19,42 +19,76 @@
#include <string>
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
namespace aapt {
namespace io {
-/**
- * InputStream interface that inherits from protobuf's ZeroCopyInputStream,
- * but adds error handling methods to better report issues.
- *
- * The code style here matches the protobuf style.
- */
-class InputStream : public ::google::protobuf::io::ZeroCopyInputStream {
+// InputStream interface that mimics protobuf's ZeroCopyInputStream,
+// with added error handling methods to better report issues.
+class InputStream {
public:
+ virtual ~InputStream() = default;
+
+ // Returns a chunk of data for reading. data and size must not be nullptr.
+ // Returns true so long as there is more data to read, returns false if an error occurred
+ // or no data remains. If an error occurred, check HadError().
+ // The stream owns the buffer returned from this method and the buffer is invalidated
+ // anytime another mutable method is called.
+ virtual bool Next(const void** data, size_t* size) = 0;
+
+ // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+ // from Next().
+ // Useful when the last block returned from Next() wasn't fully read.
+ virtual void BackUp(size_t count) = 0;
+
+ // Returns true if this InputStream can rewind. If so, Rewind() can be called.
+ virtual bool CanRewind() const { return false; };
+
+ // Rewinds the stream to the beginning so it can be read again.
+ // Returns true if the rewind succeeded.
+ // This does nothing if CanRewind() returns false.
+ virtual bool Rewind() { return false; }
+
+ // Returns the number of bytes that have been read from the stream.
+ virtual size_t ByteCount() const = 0;
+
+ // Returns an error message if HadError() returned true.
virtual std::string GetError() const { return {}; }
+ // Returns true if an error occurred. Errors are permanent.
virtual bool HadError() const = 0;
};
-/**
- * OutputStream interface that inherits from protobuf's ZeroCopyOutputStream,
- * but adds error handling methods to better report issues.
- *
- * The code style here matches the protobuf style.
- */
-class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream {
+// OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
+// with added error handling methods to better report issues.
+class OutputStream {
public:
+ virtual ~OutputStream() = default;
+
+ // Returns a buffer to which data can be written to. The data written to this buffer will
+ // eventually be written to the stream. Call BackUp() if the data written doesn't occupy the
+ // entire buffer.
+ // Return false if there was an error.
+ // The stream owns the buffer returned from this method and the buffer is invalidated
+ // anytime another mutable method is called.
+ virtual bool Next(void** data, size_t* size) = 0;
+
+ // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+ // from Next().
+ // Useful for when the last block returned from Next() wasn't fully written to.
+ virtual void BackUp(size_t count) = 0;
+
+ // Returns the number of bytes that have been written to the stream.
+ virtual size_t ByteCount() const = 0;
+
+ // Returns an error message if HadError() returned true.
virtual std::string GetError() const { return {}; }
+ // Returns true if an error occurred. Errors are permanent.
virtual bool HadError() const = 0;
};
-/**
- * Copies the data from in to out. Returns true if there was no error.
- * If there was an error, check the individual streams' HadError/GetError
- * methods.
- */
+// Copies the data from in to out. Returns false if there was an error.
+// If there was an error, check the individual streams' HadError/GetError methods.
bool Copy(OutputStream* out, InputStream* in);
} // namespace io
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 1b4d5bb..7f71589 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -38,6 +38,7 @@
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "io/BigBufferInputStream.h"
#include "io/FileSystem.h"
#include "io/ZipArchive.h"
#include "java/JavaClassGenerator.h"
@@ -168,34 +169,57 @@
int min_sdk_version_ = 0;
};
+static bool CopyInputStreamToArchive(io::InputStream* in, const std::string& out_path,
+ uint32_t compression_flags, IArchiveWriter* writer,
+ IAaptContext* context) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive");
+ }
+
+ if (!writer->WriteFile(out_path, compression_flags, in)) {
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path
+ << " to archive: " << writer->GetError());
+ return false;
+ }
+ return true;
+}
+
static bool CopyFileToArchive(io::IFile* file, const std::string& out_path,
uint32_t compression_flags,
IArchiveWriter* writer, IAaptContext* context) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
- context->GetDiagnostics()->Error(DiagMessage(file->GetSource())
- << "failed to open file");
+ context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
return false;
}
+ return CopyInputStreamToArchive(data.get(), out_path, compression_flags, writer, context);
+}
- const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
- const size_t buffer_size = data->size();
-
+static bool CopyProtoToArchive(::google::protobuf::MessageLite* proto_msg,
+ const std::string& out_path, uint32_t compression_flags,
+ IArchiveWriter* writer, IAaptContext* context) {
if (context->IsVerbose()) {
- context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path
- << " to archive");
+ context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive");
}
if (writer->StartEntry(out_path, compression_flags)) {
- if (writer->WriteEntry(buffer, buffer_size)) {
- if (writer->FinishEntry()) {
- return true;
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
+ ::google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+ if (!proto_msg->SerializeToZeroCopyStream(&adaptor)) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "failed to write " << out_path << " to archive");
+ return false;
}
}
- }
- context->GetDiagnostics()->Error(DiagMessage() << "failed to write file "
- << out_path);
+ if (writer->FinishEntry()) {
+ return true;
+ }
+ }
+ context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path
+ << " to archive: " << writer->GetError());
return false;
}
@@ -221,16 +245,9 @@
context->GetDiagnostics()->Note(msg);
}
- if (writer->StartEntry(path, ArchiveEntry::kCompress)) {
- if (writer->WriteEntry(buffer)) {
- if (writer->FinishEntry()) {
- return true;
- }
- }
- }
- context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << path
- << " to archive");
- return false;
+ io::BigBufferInputStream input_stream(&buffer);
+ return CopyInputStreamToArchive(&input_stream, path.to_string(), ArchiveEntry::kCompress, writer,
+ context);
}
static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source,
@@ -243,8 +260,7 @@
return {};
}
- std::unique_ptr<ResourceTable> table =
- DeserializeTableFromPb(pb_table, source, diag);
+ std::unique_ptr<ResourceTable> table = DeserializeTableFromPb(pb_table, source, diag);
if (!table) {
return {};
}
@@ -898,49 +914,18 @@
BigBuffer buffer(1024);
TableFlattener flattener(options_.table_flattener_options, &buffer);
if (!flattener.Consume(context_, table)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
return false;
}
- if (writer->StartEntry("resources.arsc", ArchiveEntry::kAlign)) {
- if (writer->WriteEntry(buffer)) {
- if (writer->FinishEntry()) {
- return true;
- }
- }
- }
-
- context_->GetDiagnostics()->Error(
- DiagMessage() << "failed to write resources.arsc to archive");
- return false;
+ io::BigBufferInputStream input_stream(&buffer);
+ return CopyInputStreamToArchive(&input_stream, "resources.arsc", ArchiveEntry::kAlign, writer,
+ context_);
}
bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
- // Create the file/zip entry.
- if (!writer->StartEntry("resources.arsc.flat", 0)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to open");
- return false;
- }
-
- // Make sure CopyingOutputStreamAdaptor is deleted before we call
- // writer->FinishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the
- // ZeroCopyOutputStream interface.
- CopyingOutputStreamAdaptor adaptor(writer);
-
- std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
- if (!pb_table->SerializeToZeroCopyStream(&adaptor)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to write");
- return false;
- }
- }
-
- if (!writer->FinishEntry()) {
- context_->GetDiagnostics()->Error(DiagMessage()
- << "failed to finish entry");
- return false;
- }
- return true;
+ std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
+ return CopyProtoToArchive(pb_table.get(), "resources.arsc.flat", 0, writer, context_);
}
bool WriteJavaFile(ResourceTable* table,
@@ -971,8 +956,7 @@
JavaClassGenerator generator(context_, table, java_options);
if (!generator.Generate(package_name_to_generate, out_package, &fout)) {
- context_->GetDiagnostics()->Error(DiagMessage(out_path)
- << generator.getError());
+ context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.getError());
return false;
}
@@ -1484,7 +1468,6 @@
if (options_.package_type == PackageType::kStaticLib) {
if (!FlattenTableToPb(table, writer)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc.flat");
return false;
}
} else {
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 1c9a75d..9899f80 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,12 @@
# Android Asset Packaging Tool 2.0 (AAPT2) release notes
+## Version 2.11
+### `aapt2 link ...`
+- Adds the ability to specify assets directories with the -A parameter. Assets work just like
+ assets in the original AAPT. It is not recommended to package assets with aapt2, however,
+ since the resulting APK is post-processed by other tools anyways. Assets do not get processed
+ by AAPT2, just copied, so incremental building gets slower if they are included early on.
+
## Version 2.10
### `aapt2 link ...`
- Add ability to specify package ID to compile with for regular apps (not shared or static libs).
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index 43f4ebc..e4b2020 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -127,6 +127,9 @@
private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
+ if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) {
+ return;
+ }
// The polygon of shadow (same as the original item)
float[] shadowPoly = new float[coordinates.length * 3];
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
index b2baa98..736b287 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index af48d0a..10ffd8a 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -139,7 +139,7 @@
void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
- String getWpsNfcConfigurationToken(int netId);
+ String getCurrentNetworkWpsNfcConfigurationToken();
void enableVerboseLogging(int verbose);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 824c436..ae6a679 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1437,15 +1437,15 @@
}
/**
- * Creates a configuration token describing the network referenced by {@code netId}
- * of MIME type application/vnd.wfa.wsc. Can be used to configure WiFi networks via NFC.
+ * Creates a configuration token describing the current network of MIME type
+ * application/vnd.wfa.wsc. Can be used to configure WiFi networks via NFC.
*
- * @return hex-string encoded configuration token
+ * @return hex-string encoded configuration token or null if there is no current network
* @hide
*/
- public String getWpsNfcConfigurationToken(int netId) {
+ public String getCurrentNetworkWpsNfcConfigurationToken() {
try {
- return mService.getWpsNfcConfigurationToken(netId);
+ return mService.getCurrentNetworkWpsNfcConfigurationToken();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}