Merge "Accommodate service disconnect / package update race" into oc-dev
diff --git a/api/removed.txt b/api/removed.txt
index e7b573b..f52b39a 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -73,7 +73,6 @@
method public deprecated java.lang.String getDeviceInitializerApp();
method public deprecated android.content.ComponentName getDeviceInitializerComponent();
method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>);
- field public static final deprecated int FLAG_EVICT_CE_KEY = 1; // 0x1
}
}
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 15de5c4..9f3970d 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -71,7 +71,6 @@
method public deprecated android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
method public deprecated android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>);
- field public static final deprecated int FLAG_EVICT_CE_KEY = 1; // 0x1
}
}
diff --git a/api/test-removed.txt b/api/test-removed.txt
index e7b573b..f52b39a 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -73,7 +73,6 @@
method public deprecated java.lang.String getDeviceInitializerApp();
method public deprecated android.content.ComponentName getDeviceInitializerComponent();
method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>);
- field public static final deprecated int FLAG_EVICT_CE_KEY = 1; // 0x1
}
}
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 000420f..5fedc9e 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -84,6 +84,11 @@
int main(int argc, char** argv)
{
+ // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
+ // not allowed to spawn any additional threads, but we still spawn
+ // a binder thread from userspace when we call startThreadPool().
+ // See b/36066697 for rationale
+ ProcessState::self()->setThreadPoolMaxThreadCount(0);
ProcessState::self()->startThreadPool();
const char* pname = argv[0];
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6fa0a6d..a8183f2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -976,7 +976,7 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
- if (getApplicationInfo().targetSdkVersion >= O && mActivityInfo.isFixedOrientation()) {
+ if (getApplicationInfo().targetSdkVersion > O && mActivityInfo.isFixedOrientation()) {
final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
ta.recycle();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 424e783..2de205b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -65,6 +65,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -376,7 +377,9 @@
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
if (isCredentialProtectedStorage()
- && !getSystemService(UserManager.class).isUserUnlocked() && !isBuggy()) {
+ && !getSystemService(StorageManager.class).isUserKeyUnlocked(
+ UserHandle.myUserId())
+ && !isBuggy()) {
throw new IllegalStateException("SharedPreferences in credential encrypted "
+ "storage are not available until after user is unlocked");
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 704e912..bc7fcf5 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -101,21 +101,11 @@
/**
* @hide
*/
- public static final int USER_LOCKED_ALLOWED = 0x00000040;
-
- /**
- * @hide
- */
public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
/**
* @hide
*/
- public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 0x00000100;
-
- /**
- * @hide
- */
public static final int[] LOCKABLE_FIELDS = new int[] {
USER_LOCKED_PRIORITY,
USER_LOCKED_VISIBILITY,
@@ -123,9 +113,7 @@
USER_LOCKED_LIGHTS,
USER_LOCKED_VIBRATION,
USER_LOCKED_SOUND,
- USER_LOCKED_ALLOWED,
USER_LOCKED_SHOW_BADGE,
- USER_LOCKED_AUDIO_ATTRIBUTES
};
private static final int DEFAULT_LIGHT_COLOR = 0;
@@ -273,6 +261,13 @@
/**
* @hide
*/
+ public void unlockFields(int field) {
+ mUserLockedFields &= ~field;
+ }
+
+ /**
+ * @hide
+ */
public void setDeleted(boolean deleted) {
mDeleted = deleted;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 109b5bb..de80c36 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3038,13 +3038,6 @@
*/
public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1;
- /**
- * Instead use {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}.
- * @removed
- */
- @Deprecated
- public static final int FLAG_EVICT_CE_KEY = 1;
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag=true, value={FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY})
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 0b0f048..f33c751 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -103,7 +103,8 @@
}
/**
- * Return the number of indices in the array that actually have data.
+ * Returns the number of indices in the array that actually have data. Attributes with a value
+ * of @empty are included, as this is an explicit indicator.
*
* @throws RuntimeException if the TypedArray has already been recycled.
*/
@@ -116,7 +117,8 @@
}
/**
- * Returns an index in the array that has data.
+ * Returns an index in the array that has data. Attributes with a value of @empty are included,
+ * as this is an explicit indicator.
*
* @param at The index you would like to returned, ranging from 0 to
* {@link #getIndexCount()}.
@@ -1017,7 +1019,7 @@
* @param outValue TypedValue object in which to place the attribute's
* data.
*
- * @return {@code true} if the value was retrieved, false otherwise.
+ * @return {@code true} if the value was retrieved and not @empty, {@code false} otherwise.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean getValue(@StyleableRes int index, TypedValue outValue) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d8eb950..07258fc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2858,6 +2858,14 @@
*/
static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
+ /**
+ * Flag indicating that when layout is completed we should notify
+ * that the view was entered for autofill purposes. To minimize
+ * showing autofill for views not visible to the user we evaluate
+ * user visibility which cannot be done until the view is laid out.
+ */
+ static final int PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT = 0x4000;
+
static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
static final int SCROLL_INDICATORS_NONE = 0x0000;
@@ -6496,7 +6504,7 @@
if (mParent != null) {
mParent.requestChildFocus(this, this);
- setFocusedInCluster();
+ updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
@@ -6845,8 +6853,15 @@
if (isAutofillable() && isAttachedToWindow()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
- if (enter && hasWindowFocus() && isFocused() && isVisibleToUser()) {
- afm.notifyViewEntered(this);
+ if (enter && hasWindowFocus() && isFocused()) {
+ // We have not been laid out yet, hence cannot evaluate
+ // whether this view is visible to the user, we will do
+ // the evaluation once layout is complete.
+ if (!isLaidOut()) {
+ mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
+ } else if (isVisibleToUser()) {
+ afm.notifyViewEntered(this);
+ }
} else if (!hasWindowFocus() || !isFocused()) {
afm.notifyViewExited(this);
}
@@ -9912,22 +9927,47 @@
* @hide
*/
public final void setFocusedInCluster() {
- View top = findKeyboardNavigationCluster();
- if (top == this) {
+ setFocusedInCluster(findKeyboardNavigationCluster());
+ }
+
+ private void setFocusedInCluster(View cluster) {
+ if (this instanceof ViewGroup) {
+ ((ViewGroup) this).mFocusedInCluster = null;
+ }
+ if (cluster == this) {
return;
}
ViewParent parent = mParent;
View child = this;
while (parent instanceof ViewGroup) {
- ((ViewGroup) parent).setFocusedInCluster(child);
- if (parent == top) {
- return;
+ ((ViewGroup) parent).mFocusedInCluster = child;
+ if (parent == cluster) {
+ break;
}
child = (View) parent;
parent = parent.getParent();
}
}
+ private void updateFocusedInCluster(View oldFocus, @FocusDirection int direction) {
+ if (oldFocus != null) {
+ View oldCluster = oldFocus.findKeyboardNavigationCluster();
+ View cluster = findKeyboardNavigationCluster();
+ if (oldCluster != cluster) {
+ // Going from one cluster to another, so save last-focused.
+ // This covers cluster jumps because they are always FOCUS_DOWN
+ oldFocus.setFocusedInCluster(oldCluster);
+ if (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) {
+ // This is a result of ordered navigation so consider navigation through
+ // the previous cluster "complete" and clear its last-focused memory.
+ if (oldFocus.mParent instanceof ViewGroup) {
+ ((ViewGroup) oldFocus.mParent).clearFocusedInCluster(oldFocus);
+ }
+ }
+ }
+ }
+ }
+
/**
* Returns whether this View should receive focus when the focus is restored for the view
* hierarchy containing this view.
@@ -19305,6 +19345,11 @@
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
+
+ if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
+ mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
+ notifyEnterOrExitForAutoFillIfNeeded(true);
+ }
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 18c1b8c..50593f2 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -142,7 +142,7 @@
// that is or contains a default-focus view.
private View mDefaultFocus;
// The last child of this ViewGroup which held focus within the current cluster
- private View mFocusedInCluster;
+ View mFocusedInCluster;
/**
* A Transformation used when drawing children, to
@@ -806,10 +806,6 @@
return mDefaultFocus != null || super.hasDefaultFocus();
}
- void setFocusedInCluster(View child) {
- mFocusedInCluster = child;
- }
-
/**
* Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
* it.
@@ -825,8 +821,11 @@
ViewParent parent = this;
do {
((ViewGroup) parent).mFocusedInCluster = null;
+ if (parent == top) {
+ break;
+ }
parent = parent.getParent();
- } while (parent != top && parent instanceof ViewGroup);
+ } while (parent instanceof ViewGroup);
}
@Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 748272600..6b18591 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -9812,10 +9812,9 @@
}
});
- // TODO: STOPSHIP, remove the "true" below after b/34961340 is fixed
- if (DEBUG_ENERGY_CPU || true) {
- Slog.d(TAG, "Reading cpu stats took " + (mClocks.elapsedRealtime() - startTimeMs) +
- " ms");
+ final long elapse = (mClocks.elapsedRealtime() - startTimeMs);
+ if (DEBUG_ENERGY_CPU || (elapse >= 100)) {
+ Slog.d(TAG, "Reading cpu stats took " + elapse + " ms");
}
if (mOnBatteryInternal && numWakelocks > 0) {
diff --git a/core/java/com/android/internal/os/KernelMemoryBandwidthStats.java b/core/java/com/android/internal/os/KernelMemoryBandwidthStats.java
index b5915aa..aa56e93 100644
--- a/core/java/com/android/internal/os/KernelMemoryBandwidthStats.java
+++ b/core/java/com/android/internal/os/KernelMemoryBandwidthStats.java
@@ -8,6 +8,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.BufferedReader;
+import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
@@ -24,14 +25,25 @@
public class KernelMemoryBandwidthStats {
private static final String TAG = "KernelMemoryBandwidthStats";
- final protected LongSparseLongArray mBandwidthEntries = new LongSparseLongArray();
private static final String mSysfsFile = "/sys/kernel/memory_state_time/show_stat";
private static final boolean DEBUG = false;
+ protected final LongSparseLongArray mBandwidthEntries = new LongSparseLongArray();
+ private boolean mStatsDoNotExist = false;
+
public void updateStats() {
+ if (mStatsDoNotExist) {
+ // Skip reading.
+ return;
+ }
+
StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
try (BufferedReader reader = new BufferedReader(new FileReader(mSysfsFile))) {
parseStats(reader);
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "No kernel memory bandwidth stats available");
+ mBandwidthEntries.clear();
+ mStatsDoNotExist = true;
} catch (IOException e) {
Slog.e(TAG, "Failed to read memory bandwidth: " + e.getMessage());
mBandwidthEntries.clear();
diff --git a/core/tests/coretests/src/android/content/pm/AppCacheTest.java b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
index 1567046..15dbddf 100644
--- a/core/tests/coretests/src/android/content/pm/AppCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
@@ -25,11 +25,11 @@
import android.os.ServiceManager;
import android.os.StatFs;
import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.Suppress;
import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
import java.io.File;
@@ -48,14 +48,14 @@
public final long WAIT_TIME_INCR=10*1000;
private static final long THRESHOLD=5;
private static final long ACTUAL_THRESHOLD=10;
-
+
@Override
protected void setUp() throws Exception {
super.setUp();
if(localLOGV) Log.i(TAG, "Cleaning up cache directory first");
cleanUpCacheDirectory();
}
-
+
void cleanUpDirectory(File pDir, String dirName) {
File testDir = new File(pDir, dirName);
if(!testDir.exists()) {
@@ -72,13 +72,13 @@
}
testDir.delete();
}
-
+
void cleanUpCacheDirectory() {
File testDir = mContext.getCacheDir();
if(!testDir.exists()) {
return;
}
-
+
String fList[] = testDir.list();
if(fList == null) {
testDir.delete();
@@ -93,7 +93,7 @@
}
}
}
-
+
@SmallTest
public void testDeleteAllCacheFiles() {
String testName="testDeleteAllCacheFiles";
@@ -160,9 +160,9 @@
+(blks1-blks3));
}
}
-
+
/**
- * This method opens an output file writes to it, opens the same file as an input
+ * This method opens an output file writes to it, opens the same file as an input
* stream, reads the contents and verifies the data that was written earlier can be read
*/
public void openOutFileInAppFilesDir(File pFile, String pFileOut) {
@@ -180,7 +180,7 @@
failStr(e.getMessage());
} catch (IOException e) {
failStr(e.getMessage());
- }
+ }
int count = pFileOut.getBytes().length;
byte[] buffer = new byte[count];
try {
@@ -194,8 +194,8 @@
}
String str = new String(buffer);
assertEquals(str, pFileOut);
- }
-
+ }
+
/*
* This test case verifies that output written to a file
* using Context.openFileOutput has executed successfully.
@@ -215,7 +215,7 @@
failStr(e);
}
}
-
+
@SmallTest
public void testAppCacheCreateFile() {
String fileName = "testFile1.txt";
@@ -225,7 +225,7 @@
openOutFileInAppFilesDir(file, fileOut);
cleanUpCacheDirectory();
}
-
+
@MediumTest
public void testAppCreateCacheFiles() {
File cacheDir = mContext.getCacheDir();
@@ -261,7 +261,7 @@
}
}
}
-
+
byte[] getBuffer() {
String sbuffer = "a";
for(int i = 0; i < 10; i++) {
@@ -360,7 +360,7 @@
}
assertTrue("Files should have been removed", removedFlag);
}
-
+
//createTestFiles(new File(super.getContext().getCacheDir(), "testtmp", "dir", 3)
void createTestFiles1(File cacheDir, String testFilePrefix, int numTestFiles) {
byte buffer[] = getBuffer();
@@ -439,7 +439,7 @@
}
}
}
-
+
class PackageDataObserver extends IPackageDataObserver.Stub {
public boolean retValue = false;
private boolean doneFlag = false;
@@ -455,11 +455,11 @@
return doneFlag;
}
}
-
+
IPackageManager getPm() {
return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
}
-
+
boolean invokePMDeleteAppCacheFiles() throws Exception {
try {
String packageName = mContext.getPackageName();
@@ -485,7 +485,7 @@
return false;
}
}
-
+
boolean invokePMFreeApplicationCache(long idealStorageSize) throws Exception {
try {
String packageName = mContext.getPackageName();
@@ -512,7 +512,7 @@
}
}
- boolean invokePMFreeStorage(long idealStorageSize, FreeStorageReceiver r,
+ boolean invokePMFreeStorage(long idealStorageSize, FreeStorageReceiver r,
PendingIntent pi) throws Exception {
try {
// Spin lock waiting for call back
@@ -536,7 +536,7 @@
return false;
}
}
-
+
@LargeTest
public void testDeleteAppCacheFiles() throws Exception {
String testName="testDeleteAppCacheFiles";
@@ -551,7 +551,7 @@
public boolean retValue = false;
public PackageStats stats;
private boolean doneFlag = false;
-
+
public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
throws RemoteException {
synchronized(this) {
@@ -565,7 +565,7 @@
return doneFlag;
}
}
-
+
public PackageStats invokePMGetPackageSizeInfo() throws Exception {
try {
String packageName = mContext.getPackageName();
@@ -593,7 +593,7 @@
return null;
}
}
-
+
@SmallTest
public void testGetPackageSizeInfo() throws Exception {
String testName="testGetPackageSizeInfo";
@@ -603,7 +603,7 @@
if(localLOGV) Log.i(TAG, "code="+stats.codeSize+", data="+stats.dataSize+
", cache="+stats.cacheSize);
}
-
+
@SmallTest
public void testGetSystemSharedLibraryNames() throws Exception {
try {
@@ -615,17 +615,17 @@
}
} catch (RemoteException e) {
fail("Failed invoking getSystemSharedLibraryNames with exception:" + e);
- }
+ }
}
-
+
class FreeStorageReceiver extends BroadcastReceiver {
public static final String ACTION_FREE = "com.android.unit_tests.testcallback";
private boolean doneFlag = false;
-
+
public boolean isDone() {
return doneFlag;
}
-
+
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equalsIgnoreCase(ACTION_FREE)) {
@@ -637,7 +637,7 @@
}
}
}
-
+
// TODO: flaky test, omit from LargeTest for now
//@LargeTest
public void testFreeStorage() throws Exception {
@@ -664,10 +664,10 @@
if(localLOGV || TRACKING) Log.i(TAG, "Available blocks after freeing cache"+blks3);
assertEquals(receiver.getResultCode(), 1);
mContext.unregisterReceiver(receiver);
- // Verify result
+ // Verify result
verifyTestFiles1(cacheDir, "testtmpdir", 5);
}
-
+
/* utility method used to create observer and check async call back from PackageManager.
* ClearApplicationUserData
*/
@@ -696,7 +696,7 @@
return false;
}
}
-
+
void verifyUserDataCleared(File pDir) {
if(localLOGV) Log.i(TAG, "Verifying "+pDir);
if(pDir == null) {
@@ -717,7 +717,7 @@
fail(pDir+" should be empty or contain only lib subdirectory. Found "+fileList[i]);
}
}
-
+
File getDataDir() {
try {
ApplicationInfo appInfo = getPm().getApplicationInfo(mContext.getPackageName(), 0,
@@ -727,7 +727,8 @@
throw new RuntimeException("Pacakge manager dead", e);
}
}
-
+
+ @Suppress
@LargeTest
public void testClearApplicationUserDataWithTestData() throws Exception {
File cacheDir = mContext.getCacheDir();
@@ -740,14 +741,16 @@
//confirm files dont exist
verifyUserDataCleared(getDataDir());
}
-
+
+ @Suppress
@SmallTest
public void testClearApplicationUserDataWithNoTestData() throws Exception {
assertTrue(invokePMClearApplicationUserData());
//confirm files dont exist
verifyUserDataCleared(getDataDir());
}
-
+
+ @Suppress
@LargeTest
public void testClearApplicationUserDataNoObserver() throws Exception {
getPm().clearApplicationUserData(mContext.getPackageName(), null, UserHandle.myUserId());
@@ -756,5 +759,5 @@
//confirm files dont exist
verifyUserDataCleared(getDataDir());
}
-
+
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 86ab3dc..344f3c8 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -181,9 +181,6 @@
<allow-in-power-save package="com.android.cellbroadcastreceiver" />
<allow-in-power-save package="com.android.shell" />
- <!-- STOPSHIP(b/36856786): Revert this once it is fixed properly -->
- <allow-in-power-save package="com.google.android.apps.enterprise.dmagent" />
-
<!-- These are the packages that are white-listed to be able to run as system user -->
<system-user-whitelisted-app package="com.android.settings" />
diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp
index 2771ade..60e3845 100644
--- a/libs/androidfw/AttributeResolution.cpp
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -44,8 +44,7 @@
};
class BagAttributeFinder
- : public BackTrackingAttributeFinder<BagAttributeFinder,
- const ResTable::bag_entry*> {
+ : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> {
public:
BagAttributeFinder(const ResTable::bag_entry* start,
const ResTable::bag_entry* end)
@@ -76,8 +75,7 @@
uint32_t def_style_bag_type_set_flags = 0;
if (def_style_attr != 0) {
Res_value value;
- if (theme->getAttribute(def_style_attr, &value,
- &def_style_bag_type_set_flags) >= 0) {
+ if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) {
if (value.dataType == Res_value::TYPE_REFERENCE) {
def_style_res = value.data;
}
@@ -127,18 +125,14 @@
ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType,
value.data);
}
- }
-
- if (value.dataType == Res_value::TYPE_NULL) {
- const ResTable::bag_entry* const def_style_entry =
- def_style_attr_finder.Find(cur_ident);
+ } else {
+ const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident);
if (def_style_entry != def_style_end) {
block = def_style_entry->stringBlock;
type_set_flags = def_style_type_set_flags;
value = def_style_entry->map.value;
if (kDebugStyles) {
- ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
@@ -146,29 +140,24 @@
uint32_t resid = 0;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- ssize_t new_block = theme->resolveAttributeReference(
- &value, block, &resid, &type_set_flags, &config);
+ ssize_t new_block =
+ theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
if (new_block >= 0) block = new_block;
if (kDebugStyles) {
- ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- } else {
+ } else if (value.data != Res_value::DATA_NULL_EMPTY) {
// If we still don't have a value for this attribute, try to find
// it in the theme!
- ssize_t new_block =
- theme->getAttribute(cur_ident, &value, &type_set_flags);
+ ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
if (new_block >= 0) {
if (kDebugStyles) {
- ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- new_block = res.resolveReference(&value, new_block, &resid,
- &type_set_flags, &config);
+ new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
if (new_block >= 0) block = new_block;
if (kDebugStyles) {
- ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
@@ -184,8 +173,7 @@
}
if (kDebugStyles) {
- ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident,
- value.dataType, value.data);
+ ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data);
}
// Write the final value back to Java.
@@ -198,7 +186,8 @@
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
- if (out_indices != nullptr && value.dataType != Res_value::TYPE_NULL) {
+ if (out_indices != nullptr &&
+ (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) {
indices_idx++;
out_indices[indices_idx] = ii;
}
@@ -247,8 +236,7 @@
ssize_t idx = xml_parser->indexOfStyle();
if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
if (value.dataType == value.TYPE_ATTRIBUTE) {
- if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) <
- 0) {
+ if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) {
value.dataType = Res_value::TYPE_NULL;
}
}
@@ -318,41 +306,34 @@
// We found the attribute we were looking for.
xml_parser->getAttributeValue(xml_attr_idx, &value);
if (kDebugStyles) {
- ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
- if (value.dataType == Res_value::TYPE_NULL) {
- // Walk through the style class values looking for the requested
- // attribute.
- const ResTable::bag_entry* const style_attr_entry =
- style_attr_finder.Find(cur_ident);
+ if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
+ // Walk through the style class values looking for the requested attribute.
+ const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident);
if (style_attr_entry != style_attr_end) {
// We found the attribute we were looking for.
block = style_attr_entry->stringBlock;
type_set_flags = style_type_set_flags;
value = style_attr_entry->map.value;
if (kDebugStyles) {
- ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
- if (value.dataType == Res_value::TYPE_NULL) {
- // Walk through the default style values looking for the requested
- // attribute.
- const ResTable::bag_entry* const def_style_attr_entry =
- def_style_attr_finder.Find(cur_ident);
+ if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
+ // Walk through the default style values looking for the requested attribute.
+ const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident);
if (def_style_attr_entry != def_style_attr_end) {
// We found the attribute we were looking for.
block = def_style_attr_entry->stringBlock;
type_set_flags = style_type_set_flags;
value = def_style_attr_entry->map.value;
if (kDebugStyles) {
- ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
@@ -360,35 +341,29 @@
uint32_t resid = 0;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- ssize_t new_block = theme->resolveAttributeReference(
- &value, block, &resid, &type_set_flags, &config);
+ ssize_t new_block =
+ theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
if (new_block >= 0) {
block = new_block;
}
if (kDebugStyles) {
- ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- } else {
- // If we still don't have a value for this attribute, try to find
- // it in the theme!
- ssize_t new_block =
- theme->getAttribute(cur_ident, &value, &type_set_flags);
+ } else if (value.data != Res_value::DATA_NULL_EMPTY) {
+ // If we still don't have a value for this attribute, try to find it in the theme!
+ ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
if (new_block >= 0) {
if (kDebugStyles) {
- ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- new_block = res.resolveReference(&value, new_block, &resid,
- &type_set_flags, &config);
+ new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
if (new_block >= 0) {
block = new_block;
}
if (kDebugStyles) {
- ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
@@ -404,8 +379,7 @@
}
if (kDebugStyles) {
- ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident,
- value.dataType, value.data);
+ ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data);
}
// Write the final value back to Java.
@@ -418,7 +392,7 @@
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
- if (value.dataType != Res_value::TYPE_NULL) {
+ if (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) {
indices_idx++;
// out_indices must NOT be nullptr.
@@ -502,7 +476,8 @@
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
- if (out_indices != nullptr && value.dataType != Res_value::TYPE_NULL) {
+ if (out_indices != nullptr &&
+ (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) {
indices_idx++;
out_indices[indices_idx] = ii;
}
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index f661f29b..bab8883 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -3538,7 +3538,8 @@
attrRes, bag->map.value.dataType, bag->map.value.data,
curEntry->value.dataType);
}
- if (force || curEntry->value.dataType == Res_value::TYPE_NULL) {
+ if (force || (curEntry->value.dataType == Res_value::TYPE_NULL
+ && curEntry->value.data != Res_value::DATA_NULL_EMPTY)) {
curEntry->stringBlock = bag->stringBlock;
curEntry->typeSpecFlags |= bagTypeSpecFlags;
curEntry->value = bag->map.value;
@@ -3674,7 +3675,8 @@
}
ALOGW("Too many attribute references, stopped at: 0x%08x\n", resID);
return BAD_INDEX;
- } else if (type != Res_value::TYPE_NULL) {
+ } else if (type != Res_value::TYPE_NULL
+ || te.value.data == Res_value::DATA_NULL_EMPTY) {
*outValue = te.value;
return te.stringBlock;
}
@@ -5997,16 +5999,14 @@
char locale[RESTABLE_MAX_LOCALE_LEN];
forEachConfiguration(false, false, includeSystemLocales, [&](const ResTable_config& cfg) {
- if (cfg.locale != 0) {
- cfg.getBcp47Locale(locale, mergeEquivalentLangs /* canonicalize if merging */);
+ cfg.getBcp47Locale(locale, mergeEquivalentLangs /* canonicalize if merging */);
- const auto beginIter = locales->begin();
- const auto endIter = locales->end();
+ const auto beginIter = locales->begin();
+ const auto endIter = locales->end();
- auto iter = std::lower_bound(beginIter, endIter, locale, compareString8AndCString);
- if (iter == endIter || strcmp(iter->string(), locale) != 0) {
- locales->insertAt(String8(locale), std::distance(beginIter, iter));
- }
+ auto iter = std::lower_bound(beginIter, endIter, locale, compareString8AndCString);
+ if (iter == endIter || strcmp(iter->string(), locale) != 0) {
+ locales->insertAt(String8(locale), std::distance(beginIter, iter));
}
});
}
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index d8e5abf..fcae53b 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -264,7 +264,7 @@
const ResolvedBag* bag_two = assetmanager.GetBag(app::R::style::StyleTwo);
ASSERT_NE(nullptr, bag_two);
- ASSERT_EQ(5u, bag_two->entry_count);
+ ASSERT_EQ(6u, bag_two->entry_count);
// attr_one is inherited from StyleOne.
EXPECT_EQ(app::R::attr::attr_one, bag_two->entries[0].key);
@@ -295,6 +295,11 @@
EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[4].value.dataType);
EXPECT_EQ(3u, bag_two->entries[4].value.data);
EXPECT_EQ(0, bag_two->entries[4].cookie);
+
+ EXPECT_EQ(app::R::attr::attr_empty, bag_two->entries[5].key);
+ EXPECT_EQ(Res_value::TYPE_NULL, bag_two->entries[5].value.dataType);
+ EXPECT_EQ(Res_value::DATA_NULL_EMPTY, bag_two->entries[5].value.data);
+ EXPECT_EQ(0, bag_two->entries[5].cookie);
}
TEST_F(AssetManager2Test, ResolveReferenceToResource) {
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index 1ff2ed4..2d73ce8 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -69,8 +69,8 @@
ResTable::Theme theme(table_);
ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
- std::array<uint32_t, 4> attrs{
- {R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four}};
+ std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
+ R::attr::attr_four, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/,
@@ -109,11 +109,21 @@
EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ // @empty comes from the theme, so it has the same asset cookie and changing configurations flags
+ // as the theme.
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(Res_value::DATA_NULL_EMPTY, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
}
TEST_F(AttributeResolutionXmlTest, XmlParser) {
- std::array<uint32_t, 4> attrs{
- {R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four}};
+ std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
+ R::attr::attr_four, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(),
@@ -121,7 +131,7 @@
uint32_t* values_cursor = values.data();
EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
- EXPECT_EQ(0u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(Res_value::DATA_NULL_EMPTY, values_cursor[STYLE_DATA]);
EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
@@ -150,16 +160,24 @@
EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(Res_value::DATA_NULL_UNDEFINED, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
}
TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) {
ResTable::Theme theme(table_);
ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
- std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
- R::attr::attr_four, R::attr::attr_five}};
+ std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
+ R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
- std::array<uint32_t, attrs.size()> indices;
+ std::array<uint32_t, attrs.size() + 1> indices;
ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(),
attrs.size(), values.data(), indices.data());
@@ -167,12 +185,12 @@
const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
uint32_t* values_cursor = values.data();
- EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
- EXPECT_EQ(1u, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(Res_value::DATA_NULL_EMPTY, values_cursor[STYLE_DATA]);
EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
- EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(uint32_t(-1), values_cursor[STYLE_ASSET_COOKIE]);
EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
- EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+ EXPECT_EQ(0u, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
values_cursor += STYLE_NUM_ENTRIES;
EXPECT_EQ(Res_value::TYPE_STRING, values_cursor[STYLE_TYPE]);
@@ -203,6 +221,20 @@
EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ // @empty comes from the theme, so it has the same asset cookie and changing configurations flags
+ // as the theme.
+ values_cursor += STYLE_NUM_ENTRIES;
+ EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
+ EXPECT_EQ(Res_value::DATA_NULL_EMPTY, values_cursor[STYLE_DATA]);
+ EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+ EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+ EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+ EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+ // The first element of indices contains the number of indices.
+ std::array<uint32_t, 7> expected_indices = {{6u, 0u, 1u, 2u, 3u, 4u, 5u}};
+ EXPECT_EQ(expected_indices, indices);
}
} // namespace android
diff --git a/libs/androidfw/tests/data/styles/R.h b/libs/androidfw/tests/data/styles/R.h
index 68527c7..05073a8 100644
--- a/libs/androidfw/tests/data/styles/R.h
+++ b/libs/androidfw/tests/data/styles/R.h
@@ -33,6 +33,7 @@
attr_five = 0x7f010004u,
attr_indirect = 0x7f010005u,
attr_six = 0x7f010006u,
+ attr_empty = 0x7f010007u,
};
};
diff --git a/libs/androidfw/tests/data/styles/build b/libs/androidfw/tests/data/styles/build
index 81f78b1..1ef8e6e 100755
--- a/libs/androidfw/tests/data/styles/build
+++ b/libs/androidfw/tests/data/styles/build
@@ -2,4 +2,5 @@
set -e
-aapt package -F styles.apk -M AndroidManifest.xml -S res -f
+aapt2 compile -o compiled.flata --dir res
+aapt2 link -o styles.apk --manifest AndroidManifest.xml compiled.flata
diff --git a/libs/androidfw/tests/data/styles/res/layout/layout.xml b/libs/androidfw/tests/data/styles/res/layout/layout.xml
index f3aa0f8..2c5e947 100644
--- a/libs/androidfw/tests/data/styles/res/layout/layout.xml
+++ b/libs/androidfw/tests/data/styles/res/layout/layout.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:app="http://schemas.android.com/apk/res-auto"
app:attr_four="?attr/attr_indirect"
- app:attr_three="10" />
-
+ app:attr_three="10"
+ app:attr_one="@empty" />
diff --git a/libs/androidfw/tests/data/styles/res/values/styles.xml b/libs/androidfw/tests/data/styles/res/values/styles.xml
index da592f8..3c90317 100644
--- a/libs/androidfw/tests/data/styles/res/values/styles.xml
+++ b/libs/androidfw/tests/data/styles/res/values/styles.xml
@@ -33,6 +33,12 @@
<public type="attr" name="attr_indirect" id="0x7f010005" />
<attr name="attr_indirect" />
+ <public type="attr" name="attr_six" id="0x7f010006" />
+ <attr name="attr_six" />
+
+ <public type="attr" name="attr_empty" id="0x7f010007" />
+ <attr name="attr_empty" />
+
<public type="string" name="string_one" id="0x7f030000" />
<string name="string_one">Hi</string>
@@ -48,11 +54,9 @@
<item name="attr_two">"string"</item>
<item name="attr_three">?attr/attr_indirect</item>
<item name="attr_five">@string/string_one</item>
+ <item name="attr_empty">@empty</item>
</style>
-
- <public type="attr" name="attr_six" id="0x7f010006" />
- <attr name="attr_six" />
-
+
<public type="style" name="StyleThree" id="0x7f020002" />
<style name="StyleThree">
<item name="attr_six">6</item>
diff --git a/libs/androidfw/tests/data/styles/styles.apk b/libs/androidfw/tests/data/styles/styles.apk
index d4ccb83..72abf8f 100644
--- a/libs/androidfw/tests/data/styles/styles.apk
+++ b/libs/androidfw/tests/data/styles/styles.apk
Binary files differ
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index c157a47..a19726c 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1067,7 +1067,7 @@
private void applyLevelLimits() {
int[] sampleRates = null;
Range<Integer> sampleRateRange = null, bitRates = null;
- int maxChannels = 0;
+ int maxChannels = MAX_INPUT_CHANNEL_COUNT;
String mime = mParent.getMimeType();
if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
@@ -1160,6 +1160,8 @@
if (info.containsKey("max-channel-count")) {
maxInputChannels = Utils.parseIntSafely(
info.getString("max-channel-count"), maxInputChannels);
+ } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+ maxInputChannels = 0;
}
if (info.containsKey("bitrate-range")) {
bitRates = bitRates.intersect(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c5853ca..6a9bfae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2499,7 +2499,7 @@
if (animate) {
mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, darkAmount);
mDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
mDarkAnimator.start();
} else {
setDarkAmount(darkAmount);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 0999580..41a78a7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -20,6 +20,7 @@
import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sPartitionMaxCount;
import static com.android.server.autofill.Helper.sVerbose;
import static com.android.server.autofill.Helper.bundleToString;
@@ -397,6 +398,21 @@
}
}
+ // Called by Shell command.
+ public int getMaxPartitions() {
+ synchronized (mLock) {
+ return sPartitionMaxCount;
+ }
+ }
+
+ // Called by Shell command.
+ public void setMaxPartitions(int max) {
+ Slog.i(TAG, "setMaxPartitions(): " + max);
+ synchronized (mLock) {
+ sPartitionMaxCount = max;
+ }
+ }
+
private void setDebugLocked(boolean debug) {
com.android.server.autofill.Helper.sDebug = debug;
android.view.autofill.Helper.sDebug = debug;
@@ -628,6 +644,7 @@
pw.print("Debug mode: "); pw.println(oldDebug);
pw.print("Verbose mode: "); pw.println(sVerbose);
pw.print("Disabled users: "); pw.println(mDisabledUsers);
+ pw.print("Max partitions per session: "); pw.println(sPartitionMaxCount);
final int size = mServicesCache.size();
pw.print("Cached services: ");
if (size == 0) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 1b9c86e..f3de557 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -70,9 +70,15 @@
pw.println(" get log_level ");
pw.println(" Gets the Autofill log level (off | debug | verbose).");
pw.println("");
+ pw.println(" get max_partitions");
+ pw.println(" Gets the maximum number of partitions per session.");
+ pw.println("");
pw.println(" set log_level [off | debug | verbose]");
pw.println(" Sets the Autofill log level.");
pw.println("");
+ pw.println(" set max_partitions number");
+ pw.println(" Sets the maximum number of partitions per session.");
+ pw.println("");
pw.println(" list sessions [--user USER_ID]");
pw.println(" List all pending sessions.");
pw.println("");
@@ -86,9 +92,33 @@
}
private int requestGet(PrintWriter pw) {
- if (!isNextArgLogLevel(pw, "get")) {
- return -1;
+ final String what = getNextArgRequired();
+ switch(what) {
+ case "log_level":
+ return getLogLevel(pw);
+ case "max_partitions":
+ return getMaxPartitions(pw);
+ default:
+ pw.println("Invalid set: " + what);
+ return -1;
}
+ }
+
+ private int requestSet(PrintWriter pw) {
+ final String what = getNextArgRequired();
+
+ switch(what) {
+ case "log_level":
+ return setLogLevel(pw);
+ case "max_partitions":
+ return setMaxPartitions();
+ default:
+ pw.println("Invalid set: " + what);
+ return -1;
+ }
+ }
+
+ private int getLogLevel(PrintWriter pw) {
final int logLevel = mService.getLogLevel();
switch (logLevel) {
case AutofillManager.FLAG_ADD_CLIENT_VERBOSE:
@@ -106,11 +136,8 @@
}
}
- private int requestSet(PrintWriter pw) {
- if (!isNextArgLogLevel(pw, "set")) {
- return -1;
- }
- final String logLevel = getNextArg();
+ private int setLogLevel(PrintWriter pw) {
+ final String logLevel = getNextArgRequired();
switch (logLevel.toLowerCase()) {
case "verbose":
mService.setLogLevel(AutofillManager.FLAG_ADD_CLIENT_VERBOSE);
@@ -127,6 +154,16 @@
}
}
+ private int getMaxPartitions(PrintWriter pw) {
+ pw.println(mService.getMaxPartitions());
+ return 0;
+ }
+
+ private int setMaxPartitions() {
+ mService.setMaxPartitions(Integer.parseInt(getNextArgRequired()));
+ return 0;
+ }
+
private int requestDestroy(PrintWriter pw) {
if (!isNextArgSessions(pw)) {
return -1;
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index ffcde8d..0281f73 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -26,16 +26,23 @@
/**
* Defines a logging flag that can be dynamically changed at runtime using
- * {@code cmd autofill debug [on|off]}.
+ * {@code cmd autofill set log_level debug}.
*/
public static boolean sDebug = false;
/**
* Defines a logging flag that can be dynamically changed at runtime using
- * {@code cmd autofill verbose [on|off]}.
+ * {@code cmd autofill set log_level verbose}.
*/
public static boolean sVerbose = false;
+ /**
+ * Maximum number of partitions that can be allowed in a session.
+ *
+ * <p>Can be modified using {@code cmd autofill set max_partitions}.
+ */
+ static int sPartitionMaxCount = 10;
+
private Helper() {
throw new UnsupportedOperationException("contains static members only");
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index a12ebb2..35f4fae 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -27,11 +27,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.ICancellationSignal;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.service.autofill.AutofillService;
import android.service.autofill.FillRequest;
@@ -43,6 +45,7 @@
import android.text.format.DateUtils;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.HandlerCaller;
import com.android.server.FgThread;
@@ -63,6 +66,9 @@
// How long after the last interaction with the service we would unbind
private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
+ // How long after we make a remote request to a fill service we timeout
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
+
private final Context mContext;
private final ComponentName mComponentName;
@@ -413,12 +419,18 @@
private static final class PendingFillRequest extends PendingRequest {
private final Object mLock = new Object();
+
private final WeakReference<RemoteFillService> mWeakService;
private final FillRequest mRequest;
private final IFillCallback mCallback;
private ICancellationSignal mCancellation;
+
+ @GuardedBy("mLock")
private boolean mCancelled;
+ @GuardedBy("mLock")
+ private boolean mCompleted;
+
public PendingFillRequest(FillRequest request, RemoteFillService service) {
mRequest = request;
mWeakService = new WeakReference<>(service);
@@ -443,8 +455,15 @@
@Override
public void onSuccess(FillResponse response) {
+ synchronized (mLock) {
+ if (mCompleted) {
+ return;
+ }
+ mCompleted = true;
+ }
RemoteFillService remoteService = mWeakService.get();
if (remoteService != null) {
+ service.mHandler.getHandler().removeCallbacks(PendingFillRequest.this);
remoteService.dispatchOnFillRequestSuccess(PendingFillRequest.this,
getCallingUid(), request.getFlags(), response);
}
@@ -452,13 +471,29 @@
@Override
public void onFailure(CharSequence message) {
+ synchronized (mLock) {
+ if (mCompleted) {
+ return;
+ }
+ mCompleted = true;
+ }
RemoteFillService remoteService = mWeakService.get();
if (remoteService != null) {
+ service.mHandler.getHandler().removeCallbacks(PendingFillRequest.this);
remoteService.dispatchOnFillRequestFailure(
PendingFillRequest.this, message);
}
}
};
+ service.mHandler.getHandler().postAtTime(() -> {
+ cancel();
+ try {
+ mCallback.onFailure(null);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }, PendingFillRequest.this,
+ SystemClock.uptimeMillis() + TIMEOUT_REMOTE_REQUEST_MILLIS);
}
@Override
@@ -496,10 +531,15 @@
}
private static final class PendingSaveRequest extends PendingRequest {
+ private final Object mLock = new Object();
+
private final WeakReference<RemoteFillService> mWeakService;
private final SaveRequest mRequest;
private final ISaveCallback mCallback;
+ @GuardedBy("mLock")
+ private boolean mCompleted;
+
public PendingSaveRequest(@NonNull SaveRequest request,
@NonNull RemoteFillService service) {
mRequest = request;
@@ -507,8 +547,16 @@
mCallback = new ISaveCallback.Stub() {
@Override
public void onSuccess() {
+ synchronized (mLock) {
+ if (mCompleted) {
+ return;
+ }
+ mCompleted = true;
+ }
+ cancel();
RemoteFillService service = mWeakService.get();
if (service != null) {
+ service.mHandler.getHandler().removeCallbacks(PendingSaveRequest.this);
service.dispatchOnSaveRequestSuccess(
PendingSaveRequest.this);
}
@@ -516,13 +564,29 @@
@Override
public void onFailure(CharSequence message) {
+ synchronized (mLock) {
+ if (mCompleted) {
+ return;
+ }
+ mCompleted = true;
+ }
RemoteFillService service = mWeakService.get();
if (service != null) {
+ service.mHandler.getHandler().removeCallbacks(PendingSaveRequest.this);
service.dispatchOnSaveRequestFailure(
PendingSaveRequest.this, message);
}
}
};
+ service.mHandler.getHandler().postAtTime(() -> {
+ cancel();
+ try {
+ mCallback.onFailure(null);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }, PendingSaveRequest.this,
+ SystemClock.uptimeMillis() + TIMEOUT_REMOTE_REQUEST_MILLIS);
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ef5cdd1..4746ee9 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -26,6 +26,7 @@
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sPartitionMaxCount;
import static com.android.server.autofill.Helper.sVerbose;
import static com.android.server.autofill.ViewState.STATE_AUTOFILLED;
import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION;
@@ -162,6 +163,7 @@
@GuardedBy("mLock")
private boolean mIsSaving;
+
/**
* Receiver of assist data from the app's {@link Activity}.
*/
@@ -933,7 +935,6 @@
}
}
- private static final int PARTITION_MAX_COUNT = 64;
/**
* Determines if a new partition should be started for an id.
*
@@ -947,8 +948,9 @@
}
final int numResponses = mResponses.size();
- if (numResponses >= PARTITION_MAX_COUNT) {
- Slog.e(TAG, "Cannot create more than 64 partitions. Not creating a new partition.");
+ if (numResponses >= sPartitionMaxCount) {
+ Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
+ + " reached maximum of " + sPartitionMaxCount);
return false;
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 5c57be2..f13dad7 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -2180,7 +2180,7 @@
void setRequestedOrientation(int requestedOrientation) {
if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen
- && appInfo.targetSdkVersion >= O) {
+ && appInfo.targetSdkVersion > O) {
throw new IllegalStateException("Only fullscreen activities can request orientation");
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7758516..788f21d 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -48,11 +48,13 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
public class RankingHelper implements RankingConfig {
private static final String TAG = "RankingHelper";
@@ -574,12 +576,8 @@
updateConfig();
}
- private void clearLockedFields(NotificationChannel channel) {
- int clearMask = 0;
- for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
- clearMask |= NotificationChannel.LOCKABLE_FIELDS[i];
- }
- channel.lockFields(~clearMask);
+ void clearLockedFields(NotificationChannel channel) {
+ channel.unlockFields(channel.getUserLockedFields());
}
@Override
@@ -597,6 +595,7 @@
if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
}
+ lockFieldsForUpdate(channel, updatedChannel);
r.channels.put(updatedChannel.getId(), updatedChannel);
if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
@@ -805,6 +804,35 @@
enabled ? DEFAULT_IMPORTANCE : NotificationManager.IMPORTANCE_NONE);
}
+ @VisibleForTesting
+ void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
+ update.unlockFields(update.getUserLockedFields());
+ update.lockFields(original.getUserLockedFields());
+ if (original.canBypassDnd() != update.canBypassDnd()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ }
+ if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+ }
+ if (original.getImportance() != update.getImportance()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ }
+ if (original.shouldShowLights() != update.shouldShowLights()
+ || original.getLightColor() != update.getLightColor()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
+ }
+ if (!Objects.equals(original.getSound(), update.getSound())) {
+ update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+ }
+ if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
+ || original.shouldVibrate() != update.shouldVibrate()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
+ }
+ if (original.canShowBadge() != update.canShowBadge()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
+ }
+ }
+
public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
if (filter == null) {
final int N = mSignalExtractors.length;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
index bbd4048..575e0f9 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
@@ -21,6 +21,7 @@
import android.os.ShellCommand;
import java.io.PrintWriter;
+import java.util.Locale;
class OtaDexoptShellCommand extends ShellCommand {
final IOtaDexopt mInterface;
@@ -93,7 +94,10 @@
private int runOtaProgress() throws RemoteException {
final float progress = mInterface.getProgress();
final PrintWriter pw = getOutPrintWriter();
- pw.format("%.2f", progress);
+ // Note: The float output is parsed by update_engine. It does needs to be non-localized,
+ // as it's always expected to be "0.xy," never "0,xy" or similar. So use the ROOT
+ // Locale for formatting. (b/37760573)
+ pw.format(Locale.ROOT, "%.2f", progress);
return 0;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 982561c..96ea5e5 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1248,11 +1248,11 @@
*/
@Override
int getOrientation(int candidate) {
- // We do not allow non-fullscreen apps to influence orientation at and beyond O. While we do
+ // We do not allow non-fullscreen apps to influence orientation beyond O. While we do
// throw an exception in {@link Activity#onCreate} and
// {@link Activity#setRequestedOrientation}, we also ignore the orientation here so that
// other calculations aren't affected.
- if (!fillsParent() && mTargetSdk >= O) {
+ if (!fillsParent() && mTargetSdk > O) {
// Can't specify orientation if app doesn't fill parent.
return SCREEN_ORIENTATION_UNSET;
}
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index b927e67..9c44c14 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import android.app.ActivityManager.StackId;
import android.app.RemoteAction;
@@ -285,13 +286,16 @@
if (StackId.tasksAreFloating(mStackId)) {
// Floating tasks should not be resized to the screen's bounds.
- if (bounds.width() == mTmpDisplayBounds.width() &&
+ if (mStackId == PINNED_STACK_ID && bounds.width() == mTmpDisplayBounds.width() &&
bounds.height() == mTmpDisplayBounds.height()) {
// If the bounds we are animating is the same as the fullscreen stack
// dimensions, then apply the same inset calculations that we normally do for
// the fullscreen stack, without intersecting it with the display bounds
stableBounds.inset(mTmpStableInsets);
nonDecorBounds.inset(mTmpNonDecorInsets);
+ // Move app bounds to zero to apply intersection with parent correctly. They are
+ // used only for evaluating width and height, so it's OK to move them around.
+ config.appBounds.offsetTo(0, 0);
intersectParentBounds = true;
}
width = (int) (stableBounds.width() / density);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 7bef033..30d6812 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -55,6 +55,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArrayMap;
+import android.util.Slog;
import android.util.Xml;
import java.io.BufferedInputStream;
@@ -90,6 +91,7 @@
@Mock NotificationUsageStats mUsageStats;
@Mock RankingHandler mHandler;
@Mock PackageManager mPm;
+ @Mock Context mContext;
private Notification mNotiGroupGSortA;
private Notification mNotiGroupGSortB;
@@ -113,60 +115,6 @@
MockitoAnnotations.initMocks(this);
UserHandle user = UserHandle.ALL;
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
- new String[] {ImportanceExtractor.class.getName()});
-
- mNotiGroupGSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
- .setContentTitle("A")
- .setGroup("G")
- .setSortKey("A")
- .setWhen(1205)
- .build();
- mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, mNotiGroupGSortA, user,
- null, System.currentTimeMillis()), getDefaultChannel());
-
- mNotiGroupGSortB = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
- .setContentTitle("B")
- .setGroup("G")
- .setSortKey("B")
- .setWhen(1200)
- .build();
- mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, mNotiGroupGSortB, user,
- null, System.currentTimeMillis()), getDefaultChannel());
-
- mNotiNoGroup = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
- .setContentTitle("C")
- .setWhen(1201)
- .build();
- mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, mNotiNoGroup, user,
- null, System.currentTimeMillis()), getDefaultChannel());
-
- mNotiNoGroup2 = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
- .setContentTitle("D")
- .setWhen(1202)
- .build();
- mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, mNotiNoGroup2, user,
- null, System.currentTimeMillis()), getDefaultChannel());
-
- mNotiNoGroupSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
- .setContentTitle("E")
- .setWhen(1201)
- .setSortKey("A")
- .build();
- mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, mNotiNoGroupSortA, user,
- null, System.currentTimeMillis()), getDefaultChannel());
-
- mAudioAttributes = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
- .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
- .build();
-
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
final ApplicationInfo upgrade = new ApplicationInfo();
@@ -174,6 +122,64 @@
when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
+ when(mContext.getResources()).thenReturn(
+ InstrumentationRegistry.getContext().getResources());
+ when(mContext.getPackageManager()).thenReturn(mPm);
+ when(mContext.getApplicationInfo()).thenReturn(legacy);
+
+ mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
+ new String[] {ImportanceExtractor.class.getName()});
+
+ mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentTitle("A")
+ .setGroup("G")
+ .setSortKey("A")
+ .setWhen(1205)
+ .build();
+ mRecordGroupGSortA = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, null, 0, 0, mNotiGroupGSortA, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
+
+ mNotiGroupGSortB = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentTitle("B")
+ .setGroup("G")
+ .setSortKey("B")
+ .setWhen(1200)
+ .build();
+ mRecordGroupGSortB = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, null, 0, 0, mNotiGroupGSortB, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
+
+ mNotiNoGroup = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentTitle("C")
+ .setWhen(1201)
+ .build();
+ mRecordNoGroup = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, null, 0, 0, mNotiNoGroup, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
+
+ mNotiNoGroup2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentTitle("D")
+ .setWhen(1202)
+ .build();
+ mRecordNoGroup2 = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, null, 0, 0, mNotiNoGroup2, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
+
+ mNotiNoGroupSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentTitle("E")
+ .setWhen(1201)
+ .setSortKey("A")
+ .build();
+ mRecordNoGroupSortA = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, null, 0, 0, mNotiNoGroupSortA, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
+
+ mAudioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
}
private NotificationChannel getDefaultChannel() {
@@ -229,6 +235,10 @@
assertEquals(expected.getName(), actual.getName());
}
+ private NotificationChannel getChannel() {
+ return new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ }
+
@Test
public void testFindAfterRankingWithASplitGroup() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -644,14 +654,119 @@
}
@Test
+ public void testClearLockedFields() throws Exception {
+ final NotificationChannel channel = getChannel();
+ mHelper.clearLockedFields(channel);
+ assertEquals(0, channel.getUserLockedFields());
+
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_IMPORTANCE);
+ mHelper.clearLockedFields(channel);
+ assertEquals(0, channel.getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_soundAndVibration() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setSound(new Uri.Builder().scheme("test").build(),
+ new AudioAttributes.Builder().build());
+ update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); // should be ignored
+ mHelper.updateNotificationChannel(PKG, UID, update1);
+ assertEquals(NotificationChannel.USER_LOCKED_SOUND,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ NotificationChannel update2 = getChannel();
+ update2.enableVibration(true);
+ mHelper.updateNotificationChannel(PKG, UID, update2);
+ assertEquals(NotificationChannel.USER_LOCKED_SOUND
+ | NotificationChannel.USER_LOCKED_VIBRATION,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_vibrationAndLights() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setVibrationPattern(new long[]{7945, 46 ,246});
+ mHelper.updateNotificationChannel(PKG, UID, update1);
+ assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.enableLights(true);
+ mHelper.updateNotificationChannel(PKG, UID, update2);
+ assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
+ | NotificationChannel.USER_LOCKED_LIGHTS,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_lightsAndImportance() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setLightColor(Color.GREEN);
+ mHelper.updateNotificationChannel(PKG, UID, update1);
+ assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.setImportance(IMPORTANCE_DEFAULT);
+ mHelper.updateNotificationChannel(PKG, UID, update2);
+ assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
+ | NotificationChannel.USER_LOCKED_IMPORTANCE,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_visibilityAndDndAndBadge() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true);
+ assertEquals(0,
+ mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update1 = getChannel();
+ update1.setBypassDnd(true);
+ mHelper.updateNotificationChannel(PKG, UID, update1);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ mHelper.updateNotificationChannel(PKG, UID, update2);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update3 = getChannel();
+ update3.setShowBadge(false);
+ mHelper.updateNotificationChannel(PKG, UID, update3);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY
+ | NotificationChannel.USER_LOCKED_SHOW_BADGE,
+ mHelper.getNotificationChannel(PKG, UID, update3.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
public void testDeleteNonExistentChannel() throws Exception {
mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
}
@Test
public void testGetDeletedChannel() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel = getChannel();
channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.enableLights(true);
channel.setBypassDnd(true);