Merge "[HostStubGen] Use buffered output" into main
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8dc9652..0db91f5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2579,9 +2579,10 @@
@FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final class VibrationEffect.VendorEffect extends android.os.VibrationEffect {
method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
+ method public float getAdaptiveScale();
method public long getDuration();
method public int getEffectStrength();
- method public float getLinearScale();
+ method public float getScale();
method @NonNull public android.os.PersistableBundle getVendorData();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.VendorEffect> CREATOR;
diff --git a/core/java/android/app/appfunctions/ServiceCallHelper.java b/core/java/android/app/appfunctions/ServiceCallHelper.java
new file mode 100644
index 0000000..cc882bd
--- /dev/null
+++ b/core/java/android/app/appfunctions/ServiceCallHelper.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 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.app.appfunctions;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.os.UserHandle;
+
+/**
+ * Defines a contract for establishing temporary connections to services and executing operations
+ * within a specified timeout. Implementations of this interface provide mechanisms to ensure that
+ * services are properly unbound after the operation completes or a timeout occurs.
+ *
+ * @param <T> Class of wrapped service.
+ * @hide
+ */
+public interface ServiceCallHelper<T> {
+
+ /**
+ * Initiates service binding and executes a provided method when the service connects. Unbinds
+ * the service after execution or upon timeout. Returns the result of the bindService API.
+ *
+ * <p>When the service connection was made successfully, it's the caller responsibility to
+ * report the usage is completed and can be unbound by calling {@link
+ * ServiceUsageCompleteListener#onCompleted()}.
+ *
+ * <p>This method includes a timeout mechanism to prevent the system from being stuck in a state
+ * where a service is bound indefinitely (for example, if the binder method never returns). This
+ * helps ensure that the calling app does not remain alive unnecessarily.
+ *
+ * @param intent An Intent object that describes the service that should be bound.
+ * @param bindFlags Flags used to control the binding process See {@link
+ * android.content.Context#bindService}.
+ * @param timeoutInMillis The maximum time in milliseconds to wait for the service connection.
+ * @param userHandle The UserHandle of the user for which the service should be bound.
+ * @param callback A callback to be invoked for various events. See {@link
+ * RunServiceCallCallback}.
+ */
+ boolean runServiceCall(
+ @NonNull Intent intent,
+ int bindFlags,
+ long timeoutInMillis,
+ @NonNull UserHandle userHandle,
+ @NonNull RunServiceCallCallback<T> callback);
+
+ /** An interface for clients to signal that they have finished using a bound service. */
+ interface ServiceUsageCompleteListener {
+ /**
+ * Called when a client has finished using a bound service. This indicates that the service
+ * can be safely unbound.
+ */
+ void onCompleted();
+ }
+
+ interface RunServiceCallCallback<T> {
+ /**
+ * Called when the service connection has been established. Uses {@code
+ * serviceUsageCompleteListener} to report finish using the connected service.
+ */
+ void onServiceConnected(
+ @NonNull T service,
+ @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener);
+
+ /** Called when the service connection was failed to establish. */
+ void onFailedToConnect();
+
+ /**
+ * Called when the whole operation(i.e. binding and the service call) takes longer than
+ * allowed.
+ */
+ void onTimedOut();
+ }
+}
diff --git a/core/java/android/app/appfunctions/ServiceCallHelperImpl.java b/core/java/android/app/appfunctions/ServiceCallHelperImpl.java
new file mode 100644
index 0000000..2e58546
--- /dev/null
+++ b/core/java/android/app/appfunctions/ServiceCallHelperImpl.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 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.app.appfunctions;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+
+/**
+ * An implementation of {@link android.app.appfunctions.ServiceCallHelper} that that is based on
+ * {@link Context#bindService}.
+ *
+ * @param <T> Class of wrapped service.
+ * @hide
+ */
+public class ServiceCallHelperImpl<T> implements ServiceCallHelper<T> {
+ private static final String TAG = "AppFunctionsServiceCall";
+
+ @NonNull private final Context mContext;
+ @NonNull private final Function<IBinder, T> mInterfaceConverter;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Executor mExecutor;
+
+ /**
+ * @param interfaceConverter A function responsible for converting an IBinder object into the
+ * desired service interface.
+ * @param executor An Executor instance to dispatch callback.
+ * @param context The system context.
+ */
+ public ServiceCallHelperImpl(
+ @NonNull Context context,
+ @NonNull Function<IBinder, T> interfaceConverter,
+ @NonNull Executor executor) {
+ mContext = context;
+ mInterfaceConverter = interfaceConverter;
+ mExecutor = executor;
+ }
+
+ @Override
+ public boolean runServiceCall(
+ @NonNull Intent intent,
+ int bindFlags,
+ long timeoutInMillis,
+ @NonNull UserHandle userHandle,
+ @NonNull RunServiceCallCallback<T> callback) {
+ OneOffServiceConnection serviceConnection =
+ new OneOffServiceConnection(
+ intent, bindFlags, timeoutInMillis, userHandle, callback);
+
+ return serviceConnection.bindAndRun();
+ }
+
+ private class OneOffServiceConnection
+ implements ServiceConnection, ServiceUsageCompleteListener {
+ private final Intent mIntent;
+ private final int mFlags;
+ private final long mTimeoutMillis;
+ private final UserHandle mUserHandle;
+ private final RunServiceCallCallback<T> mCallback;
+ private final Runnable mTimeoutCallback;
+
+ OneOffServiceConnection(
+ @NonNull Intent intent,
+ int flags,
+ long timeoutMillis,
+ @NonNull UserHandle userHandle,
+ @NonNull RunServiceCallCallback<T> callback) {
+ mIntent = intent;
+ mFlags = flags;
+ mTimeoutMillis = timeoutMillis;
+ mCallback = callback;
+ mTimeoutCallback =
+ () ->
+ mExecutor.execute(
+ () -> {
+ safeUnbind();
+ mCallback.onTimedOut();
+ });
+ mUserHandle = userHandle;
+ }
+
+ public boolean bindAndRun() {
+ boolean bindServiceResult =
+ mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
+
+ if (bindServiceResult) {
+ mHandler.postDelayed(mTimeoutCallback, mTimeoutMillis);
+ } else {
+ safeUnbind();
+ }
+
+ return bindServiceResult;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ T serviceInterface = mInterfaceConverter.apply(service);
+
+ mExecutor.execute(() -> mCallback.onServiceConnected(serviceInterface, this));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ safeUnbind();
+ mExecutor.execute(mCallback::onFailedToConnect);
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ safeUnbind();
+ mExecutor.execute(mCallback::onFailedToConnect);
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ safeUnbind();
+ mExecutor.execute(mCallback::onFailedToConnect);
+ }
+
+ private void safeUnbind() {
+ try {
+ mHandler.removeCallbacks(mTimeoutCallback);
+ mContext.unbindService(this);
+ } catch (Exception ex) {
+ Log.w(TAG, "Failed to unbind", ex);
+ }
+ }
+
+ @Override
+ public void onCompleted() {
+ safeUnbind();
+ }
+ }
+}
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index e68b746..f02d4a9 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -346,7 +346,7 @@
@RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)
public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) {
VibrationEffect vendorEffect = new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH,
- VendorEffect.DEFAULT_SCALE);
+ VendorEffect.DEFAULT_SCALE, VendorEffect.DEFAULT_SCALE);
vendorEffect.validate();
return vendorEffect;
}
@@ -623,7 +623,7 @@
* @hide
*/
@NonNull
- public abstract VibrationEffect scaleLinearly(float scaleFactor);
+ public abstract VibrationEffect applyAdaptiveScale(float scaleFactor);
/**
* Ensures that the effect is repeating indefinitely or not. This is a lossy operation and
@@ -948,7 +948,7 @@
/** @hide */
@NonNull
@Override
- public Composed scaleLinearly(float scaleFactor) {
+ public Composed applyAdaptiveScale(float scaleFactor) {
return applyToSegments(VibrationEffectSegment::scaleLinearly, scaleFactor);
}
@@ -1100,21 +1100,23 @@
private final PersistableBundle mVendorData;
private final int mEffectStrength;
- private final float mLinearScale;
+ private final float mScale;
+ private final float mAdaptiveScale;
/** @hide */
VendorEffect(@NonNull Parcel in) {
this(Objects.requireNonNull(
in.readPersistableBundle(VibrationEffect.class.getClassLoader())),
- in.readInt(), in.readFloat());
+ in.readInt(), in.readFloat(), in.readFloat());
}
/** @hide */
public VendorEffect(@NonNull PersistableBundle vendorData, int effectStrength,
- float linearScale) {
+ float scale, float adaptiveScale) {
mVendorData = vendorData;
mEffectStrength = effectStrength;
- mLinearScale = linearScale;
+ mScale = scale;
+ mAdaptiveScale = adaptiveScale;
}
@NonNull
@@ -1126,8 +1128,12 @@
return mEffectStrength;
}
- public float getLinearScale() {
- return mLinearScale;
+ public float getScale() {
+ return mScale;
+ }
+
+ public float getAdaptiveScale() {
+ return mAdaptiveScale;
}
/** @hide */
@@ -1175,7 +1181,8 @@
if (mEffectStrength == effectStrength) {
return this;
}
- VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mLinearScale);
+ VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mScale,
+ mAdaptiveScale);
updated.validate();
return updated;
}
@@ -1184,18 +1191,24 @@
@NonNull
@Override
public VendorEffect scale(float scaleFactor) {
- // Vendor effect strength cannot be scaled with this method.
- return this;
+ if (Float.compare(mScale, scaleFactor) == 0) {
+ return this;
+ }
+ VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor,
+ mAdaptiveScale);
+ updated.validate();
+ return updated;
}
/** @hide */
@NonNull
@Override
- public VibrationEffect scaleLinearly(float scaleFactor) {
- if (Float.compare(mLinearScale, scaleFactor) == 0) {
+ public VibrationEffect applyAdaptiveScale(float scaleFactor) {
+ if (Float.compare(mAdaptiveScale, scaleFactor) == 0) {
return this;
}
- VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor);
+ VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, mScale,
+ scaleFactor);
updated.validate();
return updated;
}
@@ -1216,29 +1229,31 @@
return false;
}
return mEffectStrength == other.mEffectStrength
- && (Float.compare(mLinearScale, other.mLinearScale) == 0)
+ && (Float.compare(mScale, other.mScale) == 0)
+ && (Float.compare(mAdaptiveScale, other.mAdaptiveScale) == 0)
&& isPersistableBundleEquals(mVendorData, other.mVendorData);
}
@Override
public int hashCode() {
// PersistableBundle does not implement hashCode, so use its size as a shortcut.
- return Objects.hash(mVendorData.size(), mEffectStrength, mLinearScale);
+ return Objects.hash(mVendorData.size(), mEffectStrength, mScale, mAdaptiveScale);
}
@Override
public String toString() {
return String.format(Locale.ROOT,
- "VendorEffect{vendorData=%s, strength=%s, scale=%.2f}",
- mVendorData, effectStrengthToString(mEffectStrength), mLinearScale);
+ "VendorEffect{vendorData=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f}",
+ mVendorData, effectStrengthToString(mEffectStrength), mScale, mAdaptiveScale);
}
/** @hide */
@Override
public String toDebugString() {
- return String.format(Locale.ROOT, "vendorEffect=%s, strength=%s, scale=%.2f",
+ return String.format(Locale.ROOT,
+ "vendorEffect=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f",
mVendorData.toShortString(), effectStrengthToString(mEffectStrength),
- mLinearScale);
+ mScale, mAdaptiveScale);
}
@Override
@@ -1251,7 +1266,8 @@
out.writeInt(PARCEL_TOKEN_VENDOR_EFFECT);
out.writePersistableBundle(mVendorData);
out.writeInt(mEffectStrength);
- out.writeFloat(mLinearScale);
+ out.writeFloat(mScale);
+ out.writeFloat(mAdaptiveScale);
}
/**
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 6a54d23..711578c 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -350,7 +350,7 @@
private static final char PARAGRAPH_SEPARATOR = '\n';
/**
- * Move the cusrot to the closest paragraph start offset.
+ * Move the cursor to the closest paragraph start offset.
*
* @param text the spannable text
* @param layout layout to be used for drawing.
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index c1e3578..471b402 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -68,6 +68,16 @@
assertThat(getBoolean("res3")).isTrue();
}
+ @Test
+ public void testFlagDisabledStringArrayElement() {
+ assertThat(getStringArray("strarr1")).isEqualTo(new String[]{"one", "two", "three"});
+ }
+
+ @Test
+ public void testFlagDisabledIntArrayElement() {
+ assertThat(getIntArray("intarr1")).isEqualTo(new int[]{1, 2, 3});
+ }
+
private boolean getBoolean(String name) {
int resId = mResources.getIdentifier(
name,
@@ -77,13 +87,22 @@
return mResources.getBoolean(resId);
}
- private String getString(String name) {
+ private String[] getStringArray(String name) {
int resId = mResources.getIdentifier(
name,
- "string",
+ "array",
"com.android.intenal.flaggedresources");
assertThat(resId).isNotEqualTo(0);
- return mResources.getString(resId);
+ return mResources.getStringArray(resId);
+ }
+
+ private int[] getIntArray(String name) {
+ int resId = mResources.getIdentifier(
+ name,
+ "array",
+ "com.android.intenal.flaggedresources");
+ assertThat(resId).isNotEqualTo(0);
+ return mResources.getIntArray(resId);
}
private String extractApkAndGetPath(int id) throws Exception {
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index bd3d944..4f76dd6 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -60,7 +60,7 @@
@RunWith(MockitoJUnitRunner.class)
public class VibrationEffectTest {
-
+ private static final float TOLERANCE = 1e-2f;
private static final String RINGTONE_URI_1 = "content://test/system/ringtone_1";
private static final String RINGTONE_URI_2 = "content://test/system/ringtone_2";
private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3";
@@ -709,7 +709,7 @@
@Test
public void testScaleWaveform() {
VibrationEffect scaledUp = TEST_WAVEFORM.scale(1.5f);
- assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), 1e-5f);
+ assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), TOLERANCE);
VibrationEffect scaledDown = TEST_WAVEFORM.scale(0.5f);
assertTrue(1f > getStepSegment(scaledDown, 0).getAmplitude());
@@ -731,11 +731,11 @@
public void testScaleVendorEffect() {
VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
- VibrationEffect scaledUp = effect.scale(1.5f);
- assertEquals(effect, scaledUp);
+ VibrationEffect.VendorEffect scaledUp = (VibrationEffect.VendorEffect) effect.scale(1.5f);
+ assertEquals(1.5f, scaledUp.getScale());
- VibrationEffect scaledDown = effect.scale(0.5f);
- assertEquals(effect, scaledDown);
+ VibrationEffect.VendorEffect scaledDown = (VibrationEffect.VendorEffect) effect.scale(0.5f);
+ assertEquals(0.5f, scaledDown.getScale());
}
@Test
@@ -755,6 +755,70 @@
}
@Test
+ public void testApplyAdaptiveScaleOneShot() {
+ VibrationEffect oneShot = VibrationEffect.createOneShot(TEST_TIMING, /* amplitude= */ 100);
+
+ VibrationEffect scaledUp = oneShot.applyAdaptiveScale(1.5f);
+ assertThat(getStepSegment(scaledUp, 0).getAmplitude()).isWithin(TOLERANCE).of(150 / 255f);
+
+ VibrationEffect scaledDown = oneShot.applyAdaptiveScale(0.5f);
+ assertThat(getStepSegment(scaledDown, 0).getAmplitude()).isWithin(TOLERANCE).of(50 / 255f);
+ }
+
+ @Test
+ public void testApplyAdaptiveScaleWaveform() {
+ VibrationEffect waveform = VibrationEffect.createWaveform(
+ new long[] { 100, 100 }, new int[] { 10, 0 }, -1);
+
+ VibrationEffect scaledUp = waveform.applyAdaptiveScale(1.5f);
+ assertThat(getStepSegment(scaledUp, 0).getAmplitude()).isWithin(TOLERANCE).of(15 / 255f);
+
+ VibrationEffect scaledDown = waveform.applyAdaptiveScale(0.5f);
+ assertThat(getStepSegment(scaledDown, 0).getAmplitude()).isWithin(TOLERANCE).of(5 / 255f);
+ }
+
+ @Test
+ public void testApplyAdaptiveScalePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ VibrationEffect scaledUp = effect.applyAdaptiveScale(1.5f);
+ assertEquals(effect, scaledUp);
+
+ VibrationEffect scaledDown = effect.applyAdaptiveScale(0.5f);
+ assertEquals(effect, scaledDown);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testApplyAdaptiveScaleVendorEffect() {
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+ VibrationEffect.VendorEffect scaledUp =
+ (VibrationEffect.VendorEffect) effect.applyAdaptiveScale(1.5f);
+ assertEquals(1.5f, scaledUp.getAdaptiveScale());
+
+ VibrationEffect.VendorEffect scaledDown =
+ (VibrationEffect.VendorEffect) effect.applyAdaptiveScale(0.5f);
+ assertEquals(0.5f, scaledDown.getAdaptiveScale());
+ }
+
+ @Test
+ public void testApplyAdaptiveScaleComposed() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+ .addEffect(VibrationEffect.createOneShot(TEST_TIMING, /* amplitude= */ 100))
+ .compose();
+
+ VibrationEffect scaledUp = effect.applyAdaptiveScale(1.5f);
+ assertThat(getPrimitiveSegment(scaledUp, 0).getScale()).isWithin(TOLERANCE).of(0.75f);
+ assertThat(getStepSegment(scaledUp, 1).getAmplitude()).isWithin(TOLERANCE).of(150 / 255f);
+
+ VibrationEffect scaledDown = effect.applyAdaptiveScale(0.5f);
+ assertThat(getPrimitiveSegment(scaledDown, 0).getScale()).isWithin(TOLERANCE).of(0.25f);
+ assertThat(getStepSegment(scaledDown, 1).getAmplitude()).isWithin(TOLERANCE).of(50 / 255f);
+ }
+
+ @Test
public void testApplyEffectStrengthToOneShotWaveformAndPrimitives() {
VibrationEffect oneShot = VibrationEffect.createOneShot(100, 100);
VibrationEffect waveform = VibrationEffect.createWaveform(new long[] { 10, 20 }, 0);
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index b0d1f71..447e980 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -220,11 +220,14 @@
field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
field public static final String CATEGORY_OTHER = "other";
field public static final String CATEGORY_PAYMENT = "payment";
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String DH = "DH";
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String ESE = "ESE";
field public static final String EXTRA_CATEGORY = "category";
field public static final String EXTRA_SERVICE_COMPONENT = "component";
field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
+ field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String UICC = "UICC";
}
public abstract class HostApduService extends android.app.Service {
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index ae63e19..2ff9829 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -75,6 +75,8 @@
public final class CardEmulation {
method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
+ method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, @Nullable String, @Nullable String);
+ method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity);
}
}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index cb97f23..79f1275 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -48,6 +48,6 @@
boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
boolean isDefaultPaymentRegistered();
- boolean overrideRoutingTable(int userHandle, String protocol, String technology);
- boolean recoverRoutingTable(int userHandle);
+ void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg);
+ void recoverRoutingTable(int userHandle);
}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index e0438ce..03372b2 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -23,6 +23,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
@@ -43,6 +44,8 @@
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.HexFormat;
import java.util.List;
@@ -148,6 +151,21 @@
* that service will be invoked directly.
*/
public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+ /**
+ * Route to Device Host (DH).
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public static final String DH = "DH";
+ /**
+ * Route to eSE.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public static final String ESE = "ESE";
+ /**
+ * Route to UICC.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public static final String UICC = "UICC";
static boolean sIsInitialized = false;
static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
@@ -865,11 +883,22 @@
sService.setServiceEnabledForCategoryOther(userId, service, status), false);
}
+ /** @hide */
+ @StringDef({
+ DH,
+ ESE,
+ UICC
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProtocolAndTechnologyRoute {}
+
/**
* Setting NFC controller routing table, which includes Protocol Route and Technology Route,
* while this Activity is in the foreground.
*
- * The parameter set to null can be used to keep current values for that entry.
+ * The parameter set to null can be used to keep current values for that entry. Either
+ * Protocol Route or Technology Route should be override when calling this API, otherwise
+ * throw {@link IllegalArgumentException}.
* <p>
* Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
* <pre>
@@ -877,26 +906,39 @@
* mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
* }</pre>
* </p>
- * Also activities must call this method when it goes to the background,
- * with all parameters set to null.
+ * Also activities must call {@link #recoverRoutingTable(Activity)}
+ * when it goes to the background. Only the package of the
+ * currently preferred service (the service set as preferred by the current foreground
+ * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
+ * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}),
+ * otherwise a call to this method will fail and throw {@link SecurityException}.
* @param activity The Activity that requests NFC controller routing table to be changed.
* @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
* @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
- * @return true if operation is successful and false otherwise
- *
+ * @throws SecurityException if the caller is not the preferred NFC service
+ * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
+ * foreground, or both protocol route and technology route are null.
+ * <p>
* This is a high risk API and only included to support mainline effort
* @hide
*/
- public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
- if (activity == null) {
- throw new NullPointerException("activity or service or category is null");
- }
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public void overrideRoutingTable(
+ @NonNull Activity activity, @ProtocolAndTechnologyRoute @Nullable String protocol,
+ @ProtocolAndTechnologyRoute @Nullable String technology) {
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- return callServiceReturn(() ->
+ if (protocol == null && technology == null) {
+ throw new IllegalArgumentException(("Both Protocol and Technology are null."));
+ }
+ callService(() ->
sService.overrideRoutingTable(
- mContext.getUser().getIdentifier(), protocol, technology), false);
+ mContext.getUser().getIdentifier(),
+ protocol,
+ technology,
+ mContext.getPackageName()));
}
/**
@@ -904,20 +946,19 @@
* which was changed by {@link #overrideRoutingTable(Activity, String, String)}
*
* @param activity The Activity that requested NFC controller routing table to be changed.
- * @return true if operation is successful and false otherwise
+ * @throws IllegalArgumentException if the caller is not in the foreground.
*
* @hide
*/
- public boolean recoverRoutingTable(Activity activity) {
- if (activity == null) {
- throw new NullPointerException("activity is null");
- }
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+ public void recoverRoutingTable(@NonNull Activity activity) {
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- return callServiceReturn(() ->
+ callService(() ->
sService.recoverRoutingTable(
- mContext.getUser().getIdentifier()), false);
+ mContext.getUser().getIdentifier()));
}
/**
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 5819b98..0fda91d 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -133,3 +133,11 @@
description: "Add Settings.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS"
bug: "358129872"
}
+
+flag {
+ name: "nfc_override_recover_routing_table"
+ is_exported: true
+ namespace: "nfc"
+ description: "Enable override and recover routing table"
+ bug: "329043523"
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
index d33433f..2fb32a7 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
@@ -16,10 +16,12 @@
package com.android.packageinstaller;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.BroadcastOptions;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.PendingIntent;
@@ -161,25 +163,31 @@
return;
}
+ // Allow the error handling actvities to start in the background.
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
switch (mStatus) {
case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
- null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
+ null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0,
+ options.toBundle());
break;
case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
if (mExtraIntent != null) {
activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
- null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
+ null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0,
+ options.toBundle());
} else {
Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
- startActivity(intent);
+ startActivity(intent, options.toBundle());
}
break;
case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", mInstallerPackageName, null);
intent.setData(uri);
- startActivity(intent);
+ startActivity(intent, options.toBundle());
break;
default:
// Do nothing. The rest of the dialogs are purely informational.
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f0c8894..823ff9f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1053,4 +1053,15 @@
<!-- List of packages for which we want to use activity info (instead of application info) for biometric prompt logo. Empty for AOSP. [DO NOT TRANSLATE] -->
<string-array name="config_useActivityLogoForBiometricPrompt" translatable="false"/>
+
+ <!--
+ Whether to enable the desktop specific feature set.
+
+ Refrain from using this from code that needs to make decisions
+ regarding the size or density of the display.
+
+ Variant owners and OEMs should override this to true when they want to
+ enable the desktop specific features.
+ -->
+ <bool name="config_enableDesktopFeatureSet">false</bool>
</resources>
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index 1938642..e2889fa 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -29,8 +29,8 @@
import android.media.AudioAttributes;
import android.os.Binder;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Slog;
+
import com.android.internal.compat.IPlatformCompat;
/**
@@ -79,6 +79,11 @@
if (restrictAudioAttributesCall() || restrictAudioAttributesAlarm()
|| restrictAudioAttributesMedia()) {
AudioAttributes attributes = record.getChannel().getAudioAttributes();
+ if (attributes == null) {
+ if (DBG) Slog.d(TAG, "missing AudioAttributes");
+ return null;
+ }
+
boolean updateAttributes = false;
if (restrictAudioAttributesCall()
&& !record.getNotification().isStyle(Notification.CallStyle.class)
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 22b4d5d..5105fd3 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1399,7 +1399,29 @@
"Package " + pkgName + " is a persistent app. "
+ "Persistent apps are not updateable.");
}
+ // When updating an sdk library, make sure that the versionMajor is
+ // changed if the targetSdkVersion and minSdkVersion have changed
+ if (parsedPackage.isSdkLibrary() && ps.getPkg() != null
+ && ps.getPkg().isSdkLibrary()) {
+ final int oldMinSdk = ps.getPkg().getMinSdkVersion();
+ final int newMinSdk = parsedPackage.getMinSdkVersion();
+ if (oldTargetSdk != newTargetSdk || oldMinSdk != newMinSdk) {
+ final int oldVersionMajor = ps.getPkg().getSdkLibVersionMajor();
+ final int newVersionMajor = parsedPackage.getSdkLibVersionMajor();
+ if (oldVersionMajor == newVersionMajor) {
+ throw new PrepareFailure(
+ PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "Failure updating " + pkgName + " as it updates"
+ + " an sdk library <"
+ + parsedPackage.getSdkLibraryName() + ">"
+ + " without changing the versionMajor, but the"
+ + " targetSdkVersion or minSdkVersion has changed."
+ );
+ }
+ }
+ }
}
+
}
PackageSetting signatureCheckPs = ps;
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index a74c4e0..b3862cc 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -134,7 +134,8 @@
return effect.resolve(mDefaultVibrationAmplitude)
.applyEffectStrength(newEffectStrength)
.scale(scaleFactor)
- .scaleLinearly(adaptiveScale);
+ // Make sure this is the last one so it is applied on top of the settings scaling.
+ .applyAdaptiveScale(adaptiveScale);
}
/**
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 8cc157c..4fc0b74 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -279,8 +279,8 @@
vendorEffect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
vendorData.setDataPosition(0);
long duration = mNativeWrapper.performVendorEffect(vendorData,
- vendorEffect.getEffectStrength(), vendorEffect.getLinearScale(),
- vibrationId);
+ vendorEffect.getEffectStrength(), vendorEffect.getScale(),
+ vendorEffect.getAdaptiveScale(), vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
notifyListenerOnVibrating(true);
@@ -459,7 +459,7 @@
long vibrationId);
private static native long performVendorEffect(long nativePtr, Parcel vendorData,
- long strength, float scale, long vibrationId);
+ long strength, float scale, float adaptiveScale, long vibrationId);
private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
long vibrationId);
@@ -518,8 +518,9 @@
/** Turns vibrator on to perform a vendor-specific effect. */
public long performVendorEffect(Parcel vendorData, long strength, float scale,
- long vibrationId) {
- return performVendorEffect(mNativePtr, vendorData, strength, scale, vibrationId);
+ float adaptiveScale, long vibrationId) {
+ return performVendorEffect(mNativePtr, vendorData, strength, scale, adaptiveScale,
+ vibrationId);
}
/** Turns vibrator on to perform effect composed of give primitives effect. */
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d169b1e..3ac91b3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3292,12 +3292,6 @@
return false;
}
- // Check if this activity is the top activity of its task - this prevents any trampolines
- // followed by enterPictureInPictureMode() calls by an activity from below in its stack.
- if (getTask().getTopMostActivity() != this) {
- return false;
- }
-
// Check to see if PiP is supported for the display this container is on.
if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
getUid())) {
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index f12930a..5c5ac28 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -198,7 +198,8 @@
}
static Aidl::VendorEffect vendorEffectFromJavaParcel(JNIEnv* env, jobject vendorData,
- jlong strength, jfloat scale) {
+ jlong strength, jfloat scale,
+ jfloat adaptiveScale) {
PersistableBundle bundle;
if (AParcel* parcel = AParcel_fromJavaParcel(env, vendorData); parcel != nullptr) {
if (binder_status_t status = bundle.readFromParcel(parcel); status == STATUS_OK) {
@@ -217,6 +218,7 @@
effect.vendorData = bundle;
effect.strength = static_cast<Aidl::EffectStrength>(strength);
effect.scale = static_cast<float>(scale);
+ effect.vendorScale = static_cast<float>(adaptiveScale);
return effect;
}
@@ -319,13 +321,14 @@
static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
jobject vendorData, jlong strength, jfloat scale,
- jlong vibrationId) {
+ jfloat adaptiveScale, jlong vibrationId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized");
return -1;
}
- Aidl::VendorEffect effect = vendorEffectFromJavaParcel(env, vendorData, strength, scale);
+ Aidl::VendorEffect effect =
+ vendorEffectFromJavaParcel(env, vendorData, strength, scale, adaptiveScale);
auto callback = wrapper->createCallback(vibrationId);
auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) {
return hal->performVendorEffect(effect, callback);
@@ -511,7 +514,7 @@
{"off", "(J)V", (void*)vibratorOff},
{"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
{"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
- {"performVendorEffect", "(JLandroid/os/Parcel;JFJ)J", (void*)vibratorPerformVendorEffect},
+ {"performVendorEffect", "(JLandroid/os/Parcel;JFFJ)J", (void*)vibratorPerformVendorEffect},
{"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
(void*)vibratorPerformComposedEffect},
{"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index 4704691..9681d74 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -281,8 +281,8 @@
@Test
@EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
- public void scale_withVendorEffect_setsEffectStrengthBasedOnSettings() {
- setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
+ public void scale_withVendorEffect_setsEffectStrengthAndScaleBasedOnSettings() {
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_MEDIUM);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
PersistableBundle vendorData = new PersistableBundle();
vendorData.putString("key", "value");
@@ -291,20 +291,27 @@
VibrationEffect.VendorEffect scaled =
(VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
+ // Notification scales up.
+ assertTrue(scaled.getScale() > 1);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
VIBRATION_INTENSITY_MEDIUM);
scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ // Notification does not scale.
+ assertEquals(1, scaled.getScale(), TOLERANCE);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ // Notification scales down.
+ assertTrue(scaled.getScale() < 1);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
// Vibration setting being bypassed will use default setting.
- assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertEquals(1, scaled.getScale(), TOLERANCE);
}
@Test
@@ -348,7 +355,7 @@
scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
USAGE_TOUCH));
// Haptic feedback does not scale.
- assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
+ assertEquals(128f / 255, scaled.getAmplitude(), TOLERANCE);
}
@Test
@@ -373,7 +380,7 @@
scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_TOUCH));
// Haptic feedback does not scale.
- assertEquals(0.5, scaled.getScale(), 1e-5);
+ assertEquals(0.5, scaled.getScale(), TOLERANCE);
}
@Test
@@ -446,7 +453,7 @@
android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
})
- public void scale_adaptiveHapticsOnVendorEffect_setsLinearScaleParameter() {
+ public void scale_adaptiveHapticsOnVendorEffect_setsAdaptiveScaleParameter() {
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
@@ -457,12 +464,12 @@
VibrationEffect.VendorEffect scaled =
(VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
- assertEquals(scaled.getLinearScale(), 0.5f);
+ assertEquals(scaled.getAdaptiveScale(), 0.5f);
mVibrationScaler.removeAdaptiveHapticsScale(USAGE_RINGTONE);
scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
- assertEquals(scaled.getLinearScale(), 1.0f);
+ assertEquals(scaled.getAdaptiveScale(), 1.0f);
}
private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 0fbdce4..bfdaa78 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -34,13 +34,15 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
-import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContextWrapper;
import android.content.pm.PackageManagerInternal;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
@@ -52,6 +54,7 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -66,11 +69,14 @@
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
import org.junit.After;
@@ -105,10 +111,12 @@
private static final int TEST_DEFAULT_AMPLITUDE = 255;
private static final float TEST_DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
- @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@@ -117,6 +125,7 @@
@Mock private VibrationConfig mVibrationConfigMock;
@Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
+ private ContextWrapper mContextSpy;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
private VibrationScaler mVibrationScaler;
@@ -149,14 +158,16 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
- Context context = InstrumentationRegistry.getContext();
- mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
- mVibrationConfigMock);
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+ when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+ mVibrationSettings = new VibrationSettings(mContextSpy,
+ new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
mockVibrators(VIBRATOR_ID);
- PowerManager.WakeLock wakeLock = context.getSystemService(
+ PowerManager.WakeLock wakeLock = mContextSpy.getSystemService(
PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mThread = new VibrationThread(wakeLock, mManagerHooks);
mThread.start();
@@ -254,6 +265,9 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
+ // No user settings scale.
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibrationEffect effect = VibrationEffect.createWaveform(
@@ -277,6 +291,9 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
+ // No user settings scale.
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
@@ -1864,6 +1881,13 @@
}
}
+ private void setUserSetting(String settingName, int value) {
+ Settings.System.putIntForUser(
+ mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+ // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
+ mVibrationSettings.mSettingObserver.onChange(false);
+ }
+
private long startThreadAndDispatcher(VibrationEffect effect) {
return startThreadAndDispatcher(CombinedVibration.createParallel(effect));
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index f009229..4013587 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1622,7 +1622,12 @@
vibrateAndWaitUntilFinished(service, vendorEffect, RINGTONE_ATTRS);
- assertThat(fakeVibrator.getAllVendorEffects()).containsExactly(vendorEffect);
+ // Compare vendor data only, ignore scale applied by device settings in this test.
+ assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1);
+ assertThat(fakeVibrator.getAllVendorEffects().get(0).getVendorData().keySet())
+ .containsExactly("key");
+ assertThat(fakeVibrator.getAllVendorEffects().get(0).getVendorData().getString("key"))
+ .isEqualTo("value");
}
@Test
@@ -1765,7 +1770,8 @@
assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1);
VibrationEffect.VendorEffect scaled = fakeVibrator.getAllVendorEffects().get(0);
assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_LIGHT);
- assertThat(scaled.getLinearScale()).isEqualTo(0.4f);
+ assertThat(scaled.getScale()).isAtMost(1); // Scale down or none if default is LOW
+ assertThat(scaled.getAdaptiveScale()).isEqualTo(0.4f);
}
@Test
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 96c3e97..031d1c2 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -140,13 +140,13 @@
@Override
public long performVendorEffect(Parcel vendorData, long strength, float scale,
- long vibrationId) {
+ float adaptiveScale, long vibrationId) {
if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) {
return 0;
}
PersistableBundle bundle = PersistableBundle.CREATOR.createFromParcel(vendorData);
recordVendorEffect(vibrationId,
- new VibrationEffect.VendorEffect(bundle, (int) strength, scale));
+ new VibrationEffect.VendorEffect(bundle, (int) strength, scale, adaptiveScale));
applyLatency(mOnLatency);
scheduleListener(mVendorEffectDuration, vibrationId);
// HAL has unknown duration for vendor effects.
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 638d594..eb63e49 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -28,6 +28,7 @@
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -114,28 +115,28 @@
/**
* In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
- * this is fixed and the nav bar shows as invisible
+ * this is fixed and the status bar shows as invisible
*/
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapePhone() {
Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
- Assume.assumeFalse(usesTaskbar)
+ Assume.assumeFalse(flicker.scenario.isTablet)
flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
/**
* In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
- * this is fixed and the nav bar shows as invisible
+ * this is fixed and the status bar shows as invisible
*/
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapeTablet() {
Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
- Assume.assumeTrue(usesTaskbar)
+ Assume.assumeTrue(flicker.scenario.isTablet)
flicker.statusBarLayerIsVisibleAtStartAndEnd()
}
@@ -149,6 +150,10 @@
@Ignore("Visibility changes depending on orientation and navigation mode")
override fun navBarLayerPositionAtStartAndEnd() {}
+ @Test
+ @Ignore("Visibility changes depending on orientation and navigation mode")
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
@@ -161,7 +166,10 @@
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+ fun taskBarLayerIsVisibleAtStartAndEndForTablets() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
+ }
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 70d762e..851ce02 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -137,8 +137,6 @@
/**
* Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of the
* transition
- *
- * Note: Large screen only
*/
@Presubmit
@Test
@@ -149,8 +147,6 @@
/**
* Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
- *
- * Note: Large screen only
*/
@Presubmit
@Test
diff --git a/tests/Internal/AndroidTest.xml b/tests/Internal/AndroidTest.xml
index 7b67e9e..2d6c650e 100644
--- a/tests/Internal/AndroidTest.xml
+++ b/tests/Internal/AndroidTest.xml
@@ -26,4 +26,12 @@
<option name="package" value="com.android.internal.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.internal.tests/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
</configuration>
\ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index ecaab12..4b745b2 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -29,7 +29,6 @@
import static org.mockito.Mockito.when;
import static java.io.File.createTempFile;
-import static java.nio.file.Files.createTempDirectory;
import android.content.Context;
import android.os.SystemClock;
@@ -45,6 +44,7 @@
import android.util.proto.ProtoInputStream;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogDataType;
@@ -77,7 +77,8 @@
@Presubmit
@RunWith(JUnit4.class)
public class PerfettoProtoLogImplTest {
- private final File mTracingDirectory = createTempDirectory("temp").toFile();
+ private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext().getFilesDir();
private final ResultWriter mWriter = new ResultWriter()
.forScenario(new ScenarioBuilder()
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 1c85e9f..a5aecc8 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -151,6 +151,7 @@
}
if (res->value != nullptr) {
+ res->value->SetFlagStatus(res->flag_status);
// Attach the comment, source and config to the value.
res->value->SetComment(std::move(res->comment));
res->value->SetSource(std::move(res->source));
@@ -546,30 +547,11 @@
});
std::string resource_type = parser->element_name();
- std::optional<StringPiece> flag =
- xml::FindAttribute(parser, "http://schemas.android.com/apk/res/android", "featureFlag");
- out_resource->flag_status = FlagStatus::NoFlag;
- if (flag) {
- auto flag_it = options_.feature_flag_values.find(flag.value());
- if (flag_it == options_.feature_flag_values.end()) {
- diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
- << "Resource flag value undefined");
- return false;
- }
- const auto& flag_properties = flag_it->second;
- if (!flag_properties.read_only) {
- diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
- << "Only read only flags may be used with resources");
- return false;
- }
- if (!flag_properties.enabled.has_value()) {
- diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
- << "Only flags with a value may be used with resources");
- return false;
- }
- out_resource->flag_status =
- flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled;
+ auto flag_status = GetFlagStatus(parser);
+ if (!flag_status) {
+ return false;
}
+ out_resource->flag_status = flag_status.value();
// The value format accepted for this resource.
uint32_t resource_format = 0u;
@@ -751,6 +733,33 @@
return false;
}
+std::optional<FlagStatus> ResourceParser::GetFlagStatus(xml::XmlPullParser* parser) {
+ auto flag_status = FlagStatus::NoFlag;
+
+ std::optional<StringPiece> flag = xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag");
+ if (flag) {
+ auto flag_it = options_.feature_flag_values.find(flag.value());
+ if (flag_it == options_.feature_flag_values.end()) {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+ << "Resource flag value undefined");
+ return {};
+ }
+ const auto& flag_properties = flag_it->second;
+ if (!flag_properties.read_only) {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+ << "Only read only flags may be used with resources");
+ return {};
+ }
+ if (!flag_properties.enabled.has_value()) {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+ << "Only flags with a value may be used with resources");
+ return {};
+ }
+ flag_status = flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled;
+ }
+ return flag_status;
+}
+
bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
ParsedResource* out_resource,
const uint32_t format) {
@@ -1657,12 +1666,18 @@
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
if (element_namespace.empty() && element_name == "item") {
+ auto flag_status = GetFlagStatus(parser);
+ if (!flag_status) {
+ error = true;
+ continue;
+ }
std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
if (!item) {
diag_->Error(android::DiagMessage(item_source) << "could not parse array item");
error = true;
continue;
}
+ item->SetFlagStatus(flag_status.value());
item->SetSource(item_source);
array->elements.emplace_back(std::move(item));
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 45d41c1..442dea8 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -85,6 +85,8 @@
private:
DISALLOW_COPY_AND_ASSIGN(ResourceParser);
+ std::optional<FlagStatus> GetFlagStatus(xml::XmlPullParser* parser);
+
std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);
// Parses the XML subtree as a StyleString (flattened XML representation for strings with
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 1cdb715..7a4f40e 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -605,12 +605,12 @@
if (!config_value->value) {
// Resource does not exist, add it now.
config_value->value = std::move(res.value);
- config_value->flag_status = res.flag_status;
} else {
// When validation is enabled, ensure that a resource cannot have multiple values defined for
// the same configuration unless protected by flags.
- auto result = validate ? ResolveFlagCollision(config_value->flag_status, res.flag_status)
- : CollisionResult::kKeepBoth;
+ auto result =
+ validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(), res.flag_status)
+ : CollisionResult::kKeepBoth;
if (result == CollisionResult::kConflict) {
result = ResolveValueCollision(config_value->value.get(), res.value.get());
}
@@ -619,7 +619,6 @@
// Insert the value ignoring for duplicate configurations
entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product));
entry->values.back()->value = std::move(res.value);
- entry->values.back()->flag_status = res.flag_status;
break;
case CollisionResult::kTakeNew:
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 4f76e7d..cba6b70 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -104,8 +104,6 @@
// The actual Value.
std::unique_ptr<Value> value;
- FlagStatus flag_status = FlagStatus::NoFlag;
-
ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
: config(config), product(product) {
}
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 166b01b..b75e87c 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -971,6 +971,16 @@
*out << "(array) [" << util::Joiner(elements, ", ") << "]";
}
+void Array::RemoveFlagDisabledElements() {
+ const auto end_iter = elements.end();
+ const auto remove_iter = std::stable_partition(
+ elements.begin(), end_iter, [](const std::unique_ptr<Item>& item) -> bool {
+ return item->GetFlagStatus() != FlagStatus::Disabled;
+ });
+
+ elements.erase(remove_iter, end_iter);
+}
+
bool Plural::Equals(const Value* value) const {
const Plural* other = ValueCast<Plural>(value);
if (!other) {
@@ -1092,6 +1102,7 @@
std::unique_ptr<T> CopyValueFields(std::unique_ptr<T> new_value, const T* value) {
new_value->SetSource(value->GetSource());
new_value->SetComment(value->GetComment());
+ new_value->SetFlagStatus(value->GetFlagStatus());
return new_value;
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 5192c2b..a1b1839 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -65,6 +65,14 @@
return translatable_;
}
+ void SetFlagStatus(FlagStatus val) {
+ flag_status_ = val;
+ }
+
+ FlagStatus GetFlagStatus() const {
+ return flag_status_;
+ }
+
// Returns the source where this value was defined.
const android::Source& GetSource() const {
return source_;
@@ -109,6 +117,10 @@
// of brevity and readability. Default implementation just calls Print().
virtual void PrettyPrint(text::Printer* printer) const;
+ // Removes any part of the value that is beind a disabled flag.
+ virtual void RemoveFlagDisabledElements() {
+ }
+
friend std::ostream& operator<<(std::ostream& out, const Value& value);
protected:
@@ -116,6 +128,7 @@
std::string comment_;
bool weak_ = false;
bool translatable_ = true;
+ FlagStatus flag_status_ = FlagStatus::NoFlag;
private:
virtual Value* TransformValueImpl(ValueTransformer& transformer) const = 0;
@@ -346,6 +359,7 @@
bool Equals(const Value* value) const override;
void Print(std::ostream* out) const override;
+ void RemoveFlagDisabledElements() override;
};
struct Plural : public TransformableValue<Plural, BaseValue<Plural>> {
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 2ecc82a..5c64089 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -246,7 +246,7 @@
message ConfigValue {
Configuration config = 1;
Value value = 2;
- uint32 flag_status = 3;
+ reserved 3;
}
// The generic meta-data for every value in a resource table.
@@ -280,6 +280,9 @@
Id id = 6;
Primitive prim = 7;
}
+
+ // The status of the flag the value is behind if any
+ uint32 flag_status = 8;
}
// A CompoundValue is an abstract type. It represents a value that is a made of other values.
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 56f5288..be63f82 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1878,7 +1878,7 @@
for (auto& type : package->types) {
for (auto& entry : type->entries) {
for (auto& config_value : entry->values) {
- if (config_value->flag_status == FlagStatus::Disabled) {
+ if (config_value->value->GetFlagStatus() == FlagStatus::Disabled) {
config_value->value->Accept(&visitor);
}
}
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index aaab315..55f5e56 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -534,8 +534,6 @@
return false;
}
- config_value->flag_status = (FlagStatus)pb_config_value.flag_status();
-
config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
&out_table->string_pool, files, out_error);
if (config_value->value == nullptr) {
@@ -877,11 +875,12 @@
return value;
}
-std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
- const android::ResStringPool& src_pool,
- const ConfigDescription& config,
- android::StringPool* value_pool,
- io::IFileCollection* files, std::string* out_error) {
+std::unique_ptr<Item> DeserializeItemFromPbInternal(const pb::Item& pb_item,
+ const android::ResStringPool& src_pool,
+ const ConfigDescription& config,
+ android::StringPool* value_pool,
+ io::IFileCollection* files,
+ std::string* out_error) {
switch (pb_item.value_case()) {
case pb::Item::kRef: {
const pb::Reference& pb_ref = pb_item.ref();
@@ -1010,6 +1009,19 @@
return {};
}
+std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
+ const android::ResStringPool& src_pool,
+ const ConfigDescription& config,
+ android::StringPool* value_pool,
+ io::IFileCollection* files, std::string* out_error) {
+ auto item =
+ DeserializeItemFromPbInternal(pb_item, src_pool, config, value_pool, files, out_error);
+ if (item) {
+ item->SetFlagStatus((FlagStatus)pb_item.flag_status());
+ }
+ return item;
+}
+
std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
std::string* out_error) {
if (!pb_node.has_element()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index c1e15bc..5772b3b 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -426,7 +426,6 @@
pb_config_value->mutable_config()->set_product(config_value->product);
SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
source_pool.get());
- pb_config_value->set_flag_status((uint32_t)config_value->flag_status);
}
}
}
@@ -720,6 +719,9 @@
if (src_pool != nullptr) {
SerializeSourceToPb(value.GetSource(), src_pool, out_value->mutable_source());
}
+ if (out_value->has_item()) {
+ out_value->mutable_item()->set_flag_status((uint32_t)value.GetFlagStatus());
+ }
}
void SerializeItemToPb(const Item& item, pb::Item* out_item) {
@@ -727,6 +729,7 @@
ValueSerializer serializer(&value, nullptr);
item.Accept(&serializer);
out_item->MergeFrom(value.item());
+ out_item->set_flag_status((uint32_t)item.GetFlagStatus());
}
void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
index 5932271..4866d2c 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -28,11 +28,13 @@
srcs: [
"res/values/bools.xml",
"res/values/bools2.xml",
+ "res/values/ints.xml",
"res/values/strings.xml",
],
out: [
"values_bools.arsc.flat",
"values_bools2.arsc.flat",
+ "values_ints.arsc.flat",
"values_strings.arsc.flat",
],
cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml
new file mode 100644
index 0000000..26a5c40
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <integer-array name="intarr1">
+ <item>1</item>
+ <item>2</item>
+ <item android:featureFlag="test.package.falseFlag">666</item>
+ <item>3</item>
+ </integer-array>
+</resources>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
index 5c0fca1..3cbb928 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
@@ -3,4 +3,11 @@
<string name="str">plain string</string>
<string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string>
+
+ <string-array name="strarr1">
+ <item>one</item>
+ <item>two</item>
+ <item android:featureFlag="test.package.falseFlag">remove</item>
+ <item android:featureFlag="test.package.trueFlag">three</item>
+ </string-array>
</resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
index e3289e2..3ac1762 100644
--- a/tools/aapt2/link/FlagDisabledResourceRemover.cpp
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
@@ -32,12 +32,17 @@
const auto remove_iter =
std::stable_partition(entry->values.begin(), end_iter,
[](const std::unique_ptr<ResourceConfigValue>& value) -> bool {
- return value->flag_status != FlagStatus::Disabled;
+ return value->value->GetFlagStatus() != FlagStatus::Disabled;
});
bool keep = remove_iter != entry->values.begin();
entry->values.erase(remove_iter, end_iter);
+
+ for (auto& value : entry->values) {
+ value->value->RemoveFlagDisabledElements();
+ }
+
return keep;
}
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1942fc11..37a039e 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -212,8 +212,8 @@
collision_result =
ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
} else {
- collision_result = ResourceTable::ResolveFlagCollision(dst_config_value->flag_status,
- src_config_value->flag_status);
+ collision_result =
+ ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
if (collision_result == CollisionResult::kConflict) {
collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
}
@@ -295,7 +295,6 @@
} else {
dst_config_value =
dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
- dst_config_value->flag_status = src_config_value->flag_status;
}
// Continue if we're taking the new resource.