Merge "Remove TODO"
diff --git a/Android.bp b/Android.bp
index 8853694..0547481 100644
--- a/Android.bp
+++ b/Android.bp
@@ -413,6 +413,8 @@
"framework-platform-compat-config",
"libcore-platform-compat-config",
"services-platform-compat-config",
+ "media-provider-platform-compat-config",
+ "services-devicepolicy-platform-compat-config",
],
sdk_version: "core_platform",
}
@@ -1460,6 +1462,11 @@
removed_api_file: "api/removed.txt",
baseline_file: ":public-api-incompatibilities-with-last-released",
},
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-public-api",
+ baseline_file: "api/lint-baseline.txt",
+ },
},
jdiff_enabled: true,
}
@@ -1486,6 +1493,11 @@
removed_api_file: "api/system-removed.txt",
baseline_file: ":system-api-incompatibilities-with-last-released"
},
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-system-api",
+ baseline_file: "api/system-lint-baseline.txt",
+ },
},
jdiff_enabled: true,
}
@@ -1584,4 +1596,4 @@
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
],
-}
\ No newline at end of file
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
index 6979f0f..48ce8ab 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
@@ -20,9 +20,9 @@
import android.os.Looper;
import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.PerfTestActivity;
import android.perftests.utils.SettingsHelper;
import android.perftests.utils.SettingsStateKeeperRule;
-import android.perftests.utils.StubActivity;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -46,8 +46,8 @@
Settings.Secure.AUTOFILL_SERVICE);
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule<StubActivity>(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -68,7 +68,7 @@
Looper.getMainLooper().getThread() == Thread.currentThread());
assertTrue("We should be running on the main thread",
Looper.myLooper() == Looper.getMainLooper());
- StubActivity activity = mActivityRule.getActivity();
+ PerfTestActivity activity = mActivityRule.getActivity();
activity.setContentView(mLayoutId);
onCreate(activity);
});
@@ -89,9 +89,9 @@
}
/**
- * Initializes the {@link StubActivity} after it was launched.
+ * Initializes the {@link PerfTestActivity} after it was launched.
*/
- protected abstract void onCreate(StubActivity activity);
+ protected abstract void onCreate(PerfTestActivity activity);
/**
* Uses the {@code settings} binary to set the autofill service.
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
index 8090826..fb5ea80 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
@@ -20,7 +20,7 @@
import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.View;
import android.widget.EditText;
@@ -39,7 +39,7 @@
}
@Override
- protected void onCreate(StubActivity activity) {
+ protected void onCreate(PerfTestActivity activity) {
View root = activity.getWindow().getDecorView();
mUsername = root.findViewById(R.id.username);
mPassword = root.findViewById(R.id.password);
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 525975d..290f178 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -13,7 +13,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.perftests.utils.StubActivity">
+ <activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
diff --git a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
index b3f8359..a320514 100644
--- a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
@@ -20,7 +20,7 @@
import android.content.Intent;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -48,7 +48,7 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
- mIntent = StubActivity.createLaunchIntent(mContext);
+ mIntent = PerfTestActivity.createLaunchIntent(mContext);
}
/**
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
index 3a80020..b9c7af4 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
@@ -19,7 +19,7 @@
import android.graphics.Paint;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -58,7 +58,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
index 3b2b8a9..d14e93e 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
@@ -26,7 +26,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.BitmapUtils;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.test.suitebuilder.annotation.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -48,8 +48,8 @@
private int[] mTestHeights = {512, 1024};
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
index 5be99d9..6b295e5 100644
--- a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
@@ -23,7 +23,7 @@
import android.graphics.Paint.FontMetricsInt;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.text.style.ReplacementSpan;
import android.util.ArraySet;
@@ -75,7 +75,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
index b34001d..b0edb11 100644
--- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
@@ -23,7 +23,7 @@
import android.graphics.drawable.ColorDrawable;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.View.MeasureSpec;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -46,7 +46,8 @@
public class ViewShowHidePerfTest {
@Rule
- public ActivityTestRule mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
index b3ea62a..270b4e5 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
@@ -18,7 +18,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
@@ -80,7 +80,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
index aa47d5b..8028f11 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
@@ -18,7 +18,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
@@ -74,7 +74,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
index e50016c..f4ad5dd 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
@@ -19,7 +19,7 @@
import android.app.Activity;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
@@ -59,7 +59,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
index 644095b..223a316 100644
--- a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
@@ -28,7 +28,7 @@
import android.os.Looper;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.View;
import android.view.ViewGroup;
@@ -72,8 +72,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
index bed173b..694e1f4 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
@@ -22,7 +22,7 @@
import android.os.Looper;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -64,8 +64,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
index 00bd8db..a546667 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
@@ -18,7 +18,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -56,7 +56,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
new file mode 100644
index 0000000..c096cd2
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 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.wm;
+
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
+
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.concurrent.TimeUnit;
+
+/** Measure the performance of internal methods in window manager service by trace tag. */
+@LargeTest
+public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
+ private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName();
+
+ @Rule
+ public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+ @Rule
+ public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule();
+
+ private final TraceMarkParser mTraceMarkParser = new TraceMarkParser(
+ "applyPostLayoutPolicy",
+ "applySurfaceChanges",
+ "AppTransitionReady",
+ "closeSurfaceTransactiom",
+ "openSurfaceTransaction",
+ "performLayout",
+ "performSurfacePlacement",
+ "prepareSurfaces",
+ "updateInputWindows",
+ "WSA#startAnimation",
+ "activityIdle",
+ "activityPaused",
+ "activityStopped",
+ "activityDestroyed",
+ "finishActivity",
+ "startActivityInner");
+
+ @Test
+ @ManualBenchmarkTest(
+ targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+ statsReport = @StatsReport(
+ flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+ | StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR))
+ public void testLaunchAndFinishActivity() throws Throwable {
+ final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long measuredTimeNs = 0;
+ boolean isTraceStarted = false;
+
+ while (state.keepRunning(measuredTimeNs)) {
+ if (!isTraceStarted && !state.isWarmingUp()) {
+ startAsyncAtrace();
+ isTraceStarted = true;
+ }
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ mActivityRule.launchActivity();
+ mActivityRule.finishActivity();
+ mActivityRule.waitForIdleSync(Stage.DESTROYED);
+ measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+ }
+
+ stopAsyncAtrace();
+
+ mTraceMarkParser.forAllSlices((key, slices) -> {
+ for (TraceMarkSlice slice : slices) {
+ state.addExtraResult(key, (long) (slice.getDurarionInSeconds() * NANOS_PER_S));
+ }
+ });
+
+ Log.i(TAG, String.valueOf(mTraceMarkParser));
+ }
+
+ private void startAsyncAtrace() throws IOException {
+ sUiAutomation.executeShellCommand("atrace -b 32768 --async_start wm");
+ // Avoid atrace isn't ready immediately.
+ SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
+ }
+
+ private void stopAsyncAtrace() throws IOException {
+ final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("atrace --async_stop");
+ final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ mTraceMarkParser.visit(line);
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 9cfc3d2..73b4a19 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -16,16 +16,13 @@
package android.wm;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN;
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
-import android.app.Activity;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
@@ -39,23 +36,16 @@
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
import android.perftests.utils.PerfManualStatusReporter;
-import android.perftests.utils.StubActivity;
import android.util.Pair;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
-import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
-import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assume;
-import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@@ -77,11 +67,10 @@
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@Rule
- public final ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule<>(
- StubActivity.class, false /* initialTouchMode */, false /* launchActivity */);
+ public final PerfTestActivityRule mActivityRule =
+ new PerfTestActivityRule(true /* launchActivity */);
private long mMeasuredTimeNs;
- private LifecycleListener mLifecycleListener;
@Parameterized.Parameter(0)
public int intervalBetweenOperations;
@@ -127,24 +116,6 @@
sUiAutomation.dropShellPermissionIdentity();
}
- @Before
- @Override
- public void setUp() {
- super.setUp();
- final Activity testActivity = mActivityRule.launchActivity(null /* intent */);
- try {
- mActivityRule.runOnUiThread(() -> testActivity.getWindow()
- .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
- } catch (Throwable ignored) { }
- mLifecycleListener = new LifecycleListener(testActivity);
- ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleListener);
- }
-
- @After
- public void tearDown() {
- ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleListener);
- }
-
/** Simulate the timing of touch. */
private void makeInterval() {
SystemClock.sleep(intervalBetweenOperations);
@@ -167,8 +138,8 @@
@ManualBenchmarkTest(
warmupDurationNs = TIME_1_S_IN_NS,
targetTestDurationNs = TIME_5_S_IN_NS,
- statsReportFlags =
- STATS_REPORT_ITERATION | STATS_REPORT_MEAN | STATS_REPORT_COEFFICIENT_VAR)
+ statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+ | StatsReport.FLAG_COEFFICIENT_VAR))
public void testRecentsAnimation() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final IActivityTaskManager atm = ActivityTaskManager.getService();
@@ -201,7 +172,7 @@
state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
if (moveRecentsToTop) {
- mLifecycleListener.waitForIdleSync(Stage.STOPPED);
+ mActivityRule.waitForIdleSync(Stage.STOPPED);
startTime = SystemClock.elapsedRealtimeNanos();
atm.startActivityFromRecents(testActivityTaskId, null /* options */);
@@ -209,7 +180,7 @@
mMeasuredTimeNs += elapsedTimeNs;
state.addExtraResult("startFromRecents", elapsedTimeNs);
- mLifecycleListener.waitForIdleSync(Stage.RESUMED);
+ mActivityRule.waitForIdleSync(Stage.RESUMED);
}
makeInterval();
@@ -223,55 +194,18 @@
}
};
+ recentsSemaphore.tryAcquire();
while (state.keepRunning(mMeasuredTimeNs)) {
- Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
+ mMeasuredTimeNs = 0;
final long startTime = SystemClock.elapsedRealtimeNanos();
atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfStart;
state.addExtraResult("start", elapsedTimeNsOfStart);
- }
- // Ensure the last round of animation callback is done.
- recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS);
- recentsSemaphore.release();
- }
-
- private static class LifecycleListener implements ActivityLifecycleCallback {
- private final Activity mTargetActivity;
- private Stage mWaitingStage;
- private Stage mReceivedStage;
-
- LifecycleListener(Activity activity) {
- mTargetActivity = activity;
- }
-
- void waitForIdleSync(Stage state) {
- synchronized (this) {
- if (state != mReceivedStage) {
- mWaitingStage = state;
- try {
- wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
- } catch (InterruptedException impossible) { }
- }
- mWaitingStage = mReceivedStage = null;
- }
- getInstrumentation().waitForIdleSync();
- }
-
- @Override
- public void onActivityLifecycleChanged(Activity activity, Stage stage) {
- if (mTargetActivity != activity) {
- return;
- }
-
- synchronized (this) {
- mReceivedStage = stage;
- if (mWaitingStage == mReceivedStage) {
- notifyAll();
- }
- }
+ // Ensure the animation callback is done.
+ Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
}
}
}
diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
index f0c474b..f43bdf8 100644
--- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
@@ -24,7 +24,7 @@
import android.os.RemoteException;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.IWindow;
@@ -57,8 +57,8 @@
public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
- public final ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule<>(StubActivity.class);
+ public final ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
/** This is only a placement to match the input parameters from {@link #getParameters}. */
@Parameterized.Parameter(0)
diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
index 4864da4..4d278c3 100644
--- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,9 +18,21 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import android.app.Activity;
import android.app.UiAutomation;
+import android.content.Intent;
+import android.perftests.utils.PerfTestActivity;
-import org.junit.Before;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.BeforeClass;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.TimeUnit;
public class WindowManagerPerfTestBase {
static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation();
@@ -28,10 +40,102 @@
static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S;
- @Before
- public void setUp() {
+ @BeforeClass
+ public static void setUpOnce() {
// In order to be closer to the real use case.
sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
sUiAutomation.executeShellCommand("wm dismiss-keyguard");
+ getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ /**
+ * Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage.
+ */
+ static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> {
+ private final Intent mStartIntent =
+ new Intent().putExtra(PerfTestActivity.INTENT_EXTRA_KEEP_SCREEN_ON, true);
+ private final LifecycleListener mLifecycleListener = new LifecycleListener();
+
+ PerfTestActivityRule() {
+ this(false /* launchActivity */);
+ }
+
+ PerfTestActivityRule(boolean launchActivity) {
+ super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ final Statement wrappedStatement = new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ ActivityLifecycleMonitorRegistry.getInstance()
+ .addLifecycleCallback(mLifecycleListener);
+ base.evaluate();
+ ActivityLifecycleMonitorRegistry.getInstance()
+ .removeLifecycleCallback(mLifecycleListener);
+ }
+ };
+ return super.apply(wrappedStatement, description);
+ }
+
+ @Override
+ protected Intent getActivityIntent() {
+ return mStartIntent;
+ }
+
+ @Override
+ public PerfTestActivity launchActivity(Intent intent) {
+ final PerfTestActivity activity = super.launchActivity(intent);
+ mLifecycleListener.setTargetActivity(activity);
+ return activity;
+ }
+
+ PerfTestActivity launchActivity() {
+ return launchActivity(mStartIntent);
+ }
+
+ void waitForIdleSync(Stage state) {
+ mLifecycleListener.waitForIdleSync(state);
+ }
+ }
+
+ static class LifecycleListener implements ActivityLifecycleCallback {
+ private Activity mTargetActivity;
+ private Stage mWaitingStage;
+ private Stage mReceivedStage;
+
+ void setTargetActivity(Activity activity) {
+ mTargetActivity = activity;
+ mReceivedStage = mWaitingStage = null;
+ }
+
+ void waitForIdleSync(Stage stage) {
+ synchronized (this) {
+ if (stage != mReceivedStage) {
+ mWaitingStage = stage;
+ try {
+ wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
+ } catch (InterruptedException impossible) { }
+ }
+ mWaitingStage = mReceivedStage = null;
+ }
+ getInstrumentation().waitForIdleSync();
+ }
+
+ @Override
+ public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+ if (mTargetActivity != activity) {
+ return;
+ }
+
+ synchronized (this) {
+ mReceivedStage = stage;
+ if (mWaitingStage == mReceivedStage) {
+ notifyAll();
+ }
+ }
+ }
}
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index ffe39e8..a83254b 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -59,27 +59,37 @@
public final class ManualBenchmarkState {
private static final String TAG = ManualBenchmarkState.class.getSimpleName();
- @IntDef(prefix = {"STATS_REPORT"}, value = {
- STATS_REPORT_MEDIAN,
- STATS_REPORT_MEAN,
- STATS_REPORT_MIN,
- STATS_REPORT_MAX,
- STATS_REPORT_PERCENTILE90,
- STATS_REPORT_PERCENTILE95,
- STATS_REPORT_STDDEV,
- STATS_REPORT_ITERATION,
- })
- public @interface StatsReport {}
+ @Target(ElementType.ANNOTATION_TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface StatsReport {
+ int FLAG_MEDIAN = 0x00000001;
+ int FLAG_MEAN = 0x00000002;
+ int FLAG_MIN = 0x00000004;
+ int FLAG_MAX = 0x00000008;
+ int FLAG_STDDEV = 0x00000010;
+ int FLAG_COEFFICIENT_VAR = 0x00000020;
+ int FLAG_ITERATION = 0x00000040;
- public static final int STATS_REPORT_MEDIAN = 0x00000001;
- public static final int STATS_REPORT_MEAN = 0x00000002;
- public static final int STATS_REPORT_MIN = 0x00000004;
- public static final int STATS_REPORT_MAX = 0x00000008;
- public static final int STATS_REPORT_PERCENTILE90 = 0x00000010;
- public static final int STATS_REPORT_PERCENTILE95 = 0x00000020;
- public static final int STATS_REPORT_STDDEV = 0x00000040;
- public static final int STATS_REPORT_COEFFICIENT_VAR = 0x00000080;
- public static final int STATS_REPORT_ITERATION = 0x00000100;
+ @Retention(RetentionPolicy.RUNTIME)
+ @IntDef(value = {
+ FLAG_MEDIAN,
+ FLAG_MEAN,
+ FLAG_MIN,
+ FLAG_MAX,
+ FLAG_STDDEV,
+ FLAG_COEFFICIENT_VAR,
+ FLAG_ITERATION,
+ })
+ @interface Flag {}
+
+ /** Defines which type of statistics should output. */
+ @Flag int flags() default -1;
+ /** An array with value 0~100 to provide the percentiles. */
+ int[] percentiles() default {};
+ }
+
+ /** It means the entire {@link StatsReport} is not given. */
+ private static final int DEFAULT_STATS_REPORT = -2;
// TODO: Tune these values.
// warm-up for duration
@@ -116,8 +126,9 @@
// The computation needs double precision, but long int is fine for final reporting.
private Stats mStats;
- private int mStatsReportFlags = STATS_REPORT_MEDIAN | STATS_REPORT_MEAN
- | STATS_REPORT_PERCENTILE90 | STATS_REPORT_PERCENTILE95 | STATS_REPORT_STDDEV;
+ private int mStatsReportFlags =
+ StatsReport.FLAG_MEDIAN | StatsReport.FLAG_MEAN | StatsReport.FLAG_STDDEV;
+ private int[] mStatsReportPercentiles = {90 , 95};
private boolean shouldReport(int statsReportFlag) {
return (mStatsReportFlags & statsReportFlag) != 0;
@@ -136,9 +147,10 @@
if (targetTestDurationNs >= 0) {
mTargetTestDurationNs = targetTestDurationNs;
}
- final int statsReportFlags = testAnnotation.statsReportFlags();
- if (statsReportFlags >= 0) {
- mStatsReportFlags = statsReportFlags;
+ final StatsReport statsReport = testAnnotation.statsReport();
+ if (statsReport != null && statsReport.flags() != DEFAULT_STATS_REPORT) {
+ mStatsReportFlags = statsReport.flags();
+ mStatsReportPercentiles = statsReport.percentiles();
}
}
@@ -189,11 +201,20 @@
}
/**
- * Adds additional result while this benchmark is running. It is used when a sequence of
+ * @return {@code true} if the benchmark is in warmup state. It can be used to skip the
+ * operations or measurements that are unnecessary while the test isn't running the
+ * actual benchmark.
+ */
+ public boolean isWarmingUp() {
+ return mState == WARMUP;
+ }
+
+ /**
+ * Adds additional result while this benchmark isn't warming up. It is used when a sequence of
* operations is executed consecutively, the duration of each operation can also be recorded.
*/
public void addExtraResult(String key, long duration) {
- if (mState != RUNNING) {
+ if (isWarmingUp()) {
return;
}
if (mExtraResults == null) {
@@ -221,31 +242,30 @@
}
private void fillStatus(Bundle status, String key, Stats stats) {
- if (shouldReport(STATS_REPORT_ITERATION)) {
+ if (shouldReport(StatsReport.FLAG_ITERATION)) {
status.putLong(key + "_iteration", stats.getSize());
}
- if (shouldReport(STATS_REPORT_MEDIAN)) {
+ if (shouldReport(StatsReport.FLAG_MEDIAN)) {
status.putLong(key + "_median", stats.getMedian());
}
- if (shouldReport(STATS_REPORT_MEAN)) {
+ if (shouldReport(StatsReport.FLAG_MEAN)) {
status.putLong(key + "_mean", Math.round(stats.getMean()));
}
- if (shouldReport(STATS_REPORT_MIN)) {
+ if (shouldReport(StatsReport.FLAG_MIN)) {
status.putLong(key + "_min", stats.getMin());
}
- if (shouldReport(STATS_REPORT_MAX)) {
+ if (shouldReport(StatsReport.FLAG_MAX)) {
status.putLong(key + "_max", stats.getMax());
}
- if (shouldReport(STATS_REPORT_PERCENTILE90)) {
- status.putLong(key + "_percentile90", stats.getPercentile90());
+ if (mStatsReportPercentiles != null) {
+ for (int percentile : mStatsReportPercentiles) {
+ status.putLong(key + "_percentile" + percentile, stats.getPercentile(percentile));
+ }
}
- if (shouldReport(STATS_REPORT_PERCENTILE95)) {
- status.putLong(key + "_percentile95", stats.getPercentile95());
- }
- if (shouldReport(STATS_REPORT_STDDEV)) {
+ if (shouldReport(StatsReport.FLAG_STDDEV)) {
status.putLong(key + "_stddev", Math.round(stats.getStandardDeviation()));
}
- if (shouldReport(STATS_REPORT_COEFFICIENT_VAR)) {
+ if (shouldReport(StatsReport.FLAG_COEFFICIENT_VAR)) {
status.putLong(key + "_cv",
Math.round((100 * stats.getStandardDeviation() / stats.getMean())));
}
@@ -276,6 +296,6 @@
public @interface ManualBenchmarkTest {
long warmupDurationNs() default -1;
long targetTestDurationNs() default -1;
- @StatsReport int statsReportFlags() default -1;
+ StatsReport statsReport() default @StatsReport(flags = DEFAULT_STATS_REPORT);
}
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
new file mode 100644
index 0000000..e934feb
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.perftests.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+/**
+ * A simple activity used for testing, e.g. performance of activity switching, or as a base
+ * container of testing view.
+ */
+public class PerfTestActivity extends Activity {
+ public static final String INTENT_EXTRA_KEEP_SCREEN_ON = "keep_screen_on";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getIntent().getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false)) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+ public static Intent createLaunchIntent(Context context) {
+ final Intent intent = new Intent();
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(context, PerfTestActivity.class);
+ return intent;
+ }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
index f650e81..fb516a8 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
@@ -16,34 +16,34 @@
package android.perftests.utils;
+import android.annotation.IntRange;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Stats {
- private long mMedian, mMin, mMax, mPercentile90, mPercentile95;
+ private long mMedian, mMin, mMax;
private double mMean, mStandardDeviation;
- private final int mSize;
+ private final List<Long> mValues;
/* Calculate stats in constructor. */
public Stats(List<Long> values) {
- // make a copy since we're modifying it
- values = new ArrayList<>(values);
final int size = values.size();
if (size < 2) {
throw new IllegalArgumentException("At least two results are necessary.");
}
+ // Make a copy since we're modifying it.
+ mValues = values = new ArrayList<>(values);
+
Collections.sort(values);
- mSize = size;
mMin = values.get(0);
mMax = values.get(values.size() - 1);
mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
values.get(size / 2);
- mPercentile90 = getPercentile(values, 90);
- mPercentile95 = getPercentile(values, 95);
for (int i = 0; i < size; ++i) {
long result = values.get(i);
@@ -59,7 +59,7 @@
}
public int getSize() {
- return mSize;
+ return mValues.size();
}
public double getMean() {
@@ -82,12 +82,8 @@
return mStandardDeviation;
}
- public long getPercentile90() {
- return mPercentile90;
- }
-
- public long getPercentile95() {
- return mPercentile95;
+ public long getPercentile(@IntRange(from = 0, to = 100) int percentile) {
+ return getPercentile(mValues, percentile);
}
private static long getPercentile(List<Long> values, int percentile) {
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
deleted file mode 100644
index 8f03f7e..0000000
--- a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.perftests.utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-
-public class StubActivity extends Activity {
- public static Intent createLaunchIntent(Context context) {
- final Intent intent = new Intent();
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClass(context, StubActivity.class);
- return intent;
- }
-}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
new file mode 100644
index 0000000..1afed3a
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 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.perftests.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility to get the slice of tracing_mark_write S,F,B,E (Async: start, finish, Sync: begin, end).
+ * Use {@link #visit(String)} to process the trace in text form. The filtered results can be
+ * obtained by {@link #forAllSlices(BiConsumer)}.
+ *
+ * @see android.os.Trace
+ */
+public class TraceMarkParser {
+ /** All slices by the name of {@link TraceMarkLine}. */
+ private final Map<String, List<TraceMarkSlice>> mSlicesMap = new HashMap<>();
+ /** The nested depth of each task-pid. */
+ private final Map<String, Integer> mDepthMap = new HashMap<>();
+ /** The start trace lines that haven't matched the corresponding end. */
+ private final Map<String, TraceMarkLine> mPendingStarts = new HashMap<>();
+
+ private final Predicate<TraceMarkLine> mTraceLineFilter;
+
+ public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
+ mTraceLineFilter = traceLineFilter;
+ }
+
+ /** Only accept the trace event with the given names. */
+ public TraceMarkParser(String... traceNames) {
+ this(line -> {
+ for (String name : traceNames) {
+ if (name.equals(line.name)) {
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+
+ /** Computes {@link TraceMarkSlice} by the given trace line. */
+ public void visit(String textTraceLine) {
+ final TraceMarkLine line = TraceMarkLine.parse(textTraceLine);
+ if (line == null) {
+ return;
+ }
+
+ if (line.isAsync) {
+ // Async-trace contains name in the start and finish event.
+ if (mTraceLineFilter.test(line)) {
+ if (line.isBegin) {
+ mPendingStarts.put(line.name, line);
+ } else {
+ final TraceMarkLine start = mPendingStarts.remove(line.name);
+ if (start != null) {
+ addSlice(start, line);
+ }
+ }
+ }
+ return;
+ }
+
+ int depth = 1;
+ if (line.isBegin) {
+ final Integer existingDepth = mDepthMap.putIfAbsent(line.taskPid, 1);
+ if (existingDepth != null) {
+ mDepthMap.put(line.taskPid, depth = existingDepth + 1);
+ }
+ // Sync-trace only contains name in the begin event.
+ if (mTraceLineFilter.test(line)) {
+ mPendingStarts.put(getSyncPendingStartKey(line, depth), line);
+ }
+ } else {
+ final Integer existingDepth = mDepthMap.get(line.taskPid);
+ if (existingDepth != null) {
+ depth = existingDepth;
+ mDepthMap.put(line.taskPid, existingDepth - 1);
+ }
+ final TraceMarkLine begin = mPendingStarts.remove(getSyncPendingStartKey(line, depth));
+ if (begin != null) {
+ addSlice(begin, line);
+ }
+ }
+ }
+
+ private static String getSyncPendingStartKey(TraceMarkLine line, int depth) {
+ return line.taskPid + "@" + depth;
+ }
+
+ private void addSlice(TraceMarkLine begin, TraceMarkLine end) {
+ mSlicesMap.computeIfAbsent(
+ begin.name, k -> new ArrayList<>()).add(new TraceMarkSlice(begin, end));
+ }
+
+ public void forAllSlices(BiConsumer<String, List<TraceMarkSlice>> consumer) {
+ for (Map.Entry<String, List<TraceMarkSlice>> entry : mSlicesMap.entrySet()) {
+ consumer.accept(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ forAllSlices((key, slices) -> {
+ double totalMs = 0;
+ for (TraceMarkSlice s : slices) {
+ totalMs += s.getDurarionInSeconds() * 1000;
+ }
+ sb.append(key).append(" count=").append(slices.size()).append(" avg=")
+ .append(totalMs / slices.size()).append("ms\n");
+ });
+
+ if (!mPendingStarts.isEmpty()) {
+ sb.append("[Warning] Unresolved events:").append(mPendingStarts).append("\n");
+ }
+ return sb.toString();
+ }
+
+ public static class TraceMarkSlice {
+ public final TraceMarkLine begin;
+ public final TraceMarkLine end;
+
+ TraceMarkSlice(TraceMarkLine begin, TraceMarkLine end) {
+ this.begin = begin;
+ this.end = end;
+ }
+
+ public double getDurarionInSeconds() {
+ return end.timestamp - begin.timestamp;
+ }
+ }
+
+ // taskPid timestamp name
+ // # Async:
+ // Binder:129_F-349 ( 1296) [003] ...1 12.2776: tracing_mark_write: S|1296|launching: a.test|0
+ // android.anim-135 ( 1296) [005] ...1 12.3361: tracing_mark_write: F|1296|launching: a.test|0
+ // # Normal:
+ // Binder:129_6-315 ( 1296) [007] ...1 97.4576: tracing_mark_write: B|1296|relayoutWindow: xxx
+ // ... there may have other nested begin/end
+ // Binder:129_6-315 ( 1296) [007] ...1 97.4580: tracing_mark_write: E|1296
+ public static class TraceMarkLine {
+ static final String EVENT_KEYWORD = ": tracing_mark_write: ";
+ static final char ASYNC_START = 'S';
+ static final char ASYNC_FINISH = 'F';
+ static final char SYNC_BEGIN = 'B';
+ static final char SYNC_END = 'E';
+
+ public final String taskPid;
+ public final double timestamp;
+ public final String name;
+ public final boolean isAsync;
+ public final boolean isBegin;
+
+ TraceMarkLine(String rawLine, int typePos, int type) throws IllegalArgumentException {
+ taskPid = rawLine.substring(0, rawLine.indexOf('(')).trim();
+ final int timeEnd = rawLine.indexOf(':', taskPid.length());
+ if (timeEnd < 0) {
+ throw new IllegalArgumentException("Timestamp end not found");
+ }
+ final int timeBegin = rawLine.lastIndexOf(' ', timeEnd);
+ if (timeBegin < 0) {
+ throw new IllegalArgumentException("Timestamp start not found");
+ }
+ timestamp = Double.parseDouble(rawLine.substring(timeBegin, timeEnd));
+ isAsync = type == ASYNC_START || type == ASYNC_FINISH;
+ isBegin = type == ASYNC_START || type == SYNC_BEGIN;
+
+ if (!isAsync && !isBegin) {
+ name = "";
+ } else {
+ // Get the position of the second '|' from "S|1234|name".
+ final int nameBegin = rawLine.indexOf('|', typePos + 2) + 1;
+ if (nameBegin == 0) {
+ throw new IllegalArgumentException("Name begin not found");
+ }
+ if (isAsync) {
+ // Get the name from "S|1234|name|0".
+ name = rawLine.substring(nameBegin, rawLine.lastIndexOf('|'));
+ } else {
+ name = rawLine.substring(nameBegin);
+ }
+ }
+ }
+
+ static TraceMarkLine parse(String rawLine) {
+ final int eventPos = rawLine.indexOf(EVENT_KEYWORD);
+ if (eventPos < 0) {
+ return null;
+ }
+ final int typePos = eventPos + EVENT_KEYWORD.length();
+ if (typePos >= rawLine.length()) {
+ return null;
+ }
+ final int type = rawLine.charAt(typePos);
+ if (type != ASYNC_START && type != ASYNC_FINISH
+ && type != SYNC_BEGIN && type != SYNC_END) {
+ return null;
+ }
+
+ try {
+ return new TraceMarkLine(rawLine, typePos, type);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "TraceMarkLine{pid=" + taskPid + " time=" + timestamp + " name=" + name
+ + " async=" + isAsync + " begin=" + isBegin + "}";
+ }
+ }
+}
diff --git a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
index 9039f92..e27670c 100644
--- a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
+++ b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
@@ -17,10 +17,13 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import java.util.List;
+
/**
* Access to the service that keeps track of device idleness and drives low power mode based on
* that.
@@ -66,4 +69,19 @@
return new String[0];
}
}
+
+ /**
+ * Add the specified packages to the power save whitelist.
+ *
+ * @return the number of packages that were successfully added to the whitelist
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public int addPowerSaveWhitelistApps(@NonNull List<String> packageNames) {
+ try {
+ return mService.addPowerSaveWhitelistApps(packageNames);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return 0;
+ }
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index 9d5becb..20fb000 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -21,6 +21,7 @@
/** @hide */
interface IDeviceIdleController {
void addPowerSaveWhitelistApp(String name);
+ int addPowerSaveWhitelistApps(in List<String> packageNames);
void removePowerSaveWhitelistApp(String name);
/* Removes an app from the system whitelist. Calling restoreSystemPowerWhitelistApp will add
the app back into the system whitelist */
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 20ee064..4ee46f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -105,6 +105,8 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.stream.Collectors;
/**
@@ -1549,11 +1551,20 @@
if (DEBUG) {
Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
}
+ addPowerSaveWhitelistApps(Collections.singletonList(name));
+ }
+
+ @Override
+ public int addPowerSaveWhitelistApps(List<String> packageNames) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "addPowerSaveWhitelistApps(name = " + packageNames + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
try {
- addPowerSaveWhitelistAppInternal(name);
+ return addPowerSaveWhitelistAppsInternal(packageNames);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2188,21 +2199,35 @@
}
}
- public boolean addPowerSaveWhitelistAppInternal(String name) {
+ private int addPowerSaveWhitelistAppsInternal(List<String> pkgNames) {
+ int numAdded = 0;
+ int numErrors = 0;
synchronized (this) {
- try {
- ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
- PackageManager.MATCH_ANY_USER);
- if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid)) == null) {
- reportPowerSaveWhitelistChangedLocked();
- updateWhitelistAppIdsLocked();
- writeConfigFileLocked();
+ for (int i = pkgNames.size() - 1; i >= 0; --i) {
+ final String name = pkgNames.get(i);
+ if (name == null) {
+ numErrors++;
+ continue;
}
- return true;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
+ try {
+ ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
+ PackageManager.MATCH_ANY_USER);
+ if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid))
+ == null) {
+ numAdded++;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Tried to add unknown package to power save whitelist: " + name);
+ numErrors++;
+ }
+ }
+ if (numAdded > 0) {
+ reportPowerSaveWhitelistChangedLocked();
+ updateWhitelistAppIdsLocked();
+ writeConfigFileLocked();
}
}
+ return pkgNames.size() - numErrors;
}
public boolean removePowerSaveWhitelistAppInternal(String name) {
@@ -4070,7 +4095,8 @@
char op = arg.charAt(0);
String pkg = arg.substring(1);
if (op == '+') {
- if (addPowerSaveWhitelistAppInternal(pkg)) {
+ if (addPowerSaveWhitelistAppsInternal(Collections.singletonList(pkg))
+ == 1) {
pw.println("Added: " + pkg);
} else {
pw.println("Unknown package: " + pkg);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index b97da59..aa7696d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -17,13 +17,8 @@
package com.android.server.job.restrictions;
import android.app.job.JobParameters;
-import android.content.Context;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.Temperature;
-import android.util.Slog;
+import android.os.PowerManager;
+import android.os.PowerManager.OnThermalStatusChangedListener;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.IndentingPrintWriter;
@@ -36,31 +31,29 @@
private volatile boolean mIsThermalRestricted = false;
+ private PowerManager mPowerManager;
+
public ThermalStatusRestriction(JobSchedulerService service) {
super(service, JobParameters.REASON_DEVICE_THERMAL);
}
@Override
public void onSystemServicesReady() {
- final IThermalService thermalService = IThermalService.Stub.asInterface(
- ServiceManager.getService(Context.THERMAL_SERVICE));
- if (thermalService != null) {
- try {
- thermalService.registerThermalStatusListener(new IThermalStatusListener.Stub() {
- @Override
- public void onStatusChange(int status) {
- final boolean shouldBeActive = status >= Temperature.THROTTLING_SEVERE;
- if (mIsThermalRestricted == shouldBeActive) {
- return;
- }
- mIsThermalRestricted = shouldBeActive;
- mService.onControllerStateChanged();
- }
- });
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register thermal callback.", e);
+ mPowerManager = mService.getContext().getSystemService(PowerManager.class);
+ // Use MainExecutor
+ mPowerManager.addThermalStatusListener(new OnThermalStatusChangedListener() {
+ @Override
+ public void onThermalStatusChanged(int status) {
+ // This is called on the main thread. Do not do any slow operations in it.
+ // mService.onControllerStateChanged() will just post a message, which is okay.
+ final boolean shouldBeActive = status >= PowerManager.THERMAL_STATUS_SEVERE;
+ if (mIsThermalRestricted == shouldBeActive) {
+ return;
+ }
+ mIsThermalRestricted = shouldBeActive;
+ mService.onControllerStateChanged();
}
- }
+ });
}
@Override
diff --git a/api/current.txt b/api/current.txt
index 677e8c2..b176675 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5872,6 +5872,7 @@
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(String);
method public java.util.Map<java.lang.String,android.app.AutomaticZenRule> getAutomaticZenRules();
+ method @Nullable public android.app.NotificationManager.Policy getConsolidatedNotificationPolicy();
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(String);
@@ -9805,7 +9806,7 @@
method public abstract java.io.File[] getExternalCacheDirs();
method @Nullable public abstract java.io.File getExternalFilesDir(@Nullable String);
method public abstract java.io.File[] getExternalFilesDirs(String);
- method public abstract java.io.File[] getExternalMediaDirs();
+ method @Deprecated public abstract java.io.File[] getExternalMediaDirs();
method public abstract java.io.File getFileStreamPath(String);
method public abstract java.io.File getFilesDir();
method public java.util.concurrent.Executor getMainExecutor();
@@ -12402,6 +12403,8 @@
public class Resources {
ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
+ method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int);
+ method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider);
method public final void finishPreloading();
method public final void flushLayoutCache();
method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimatorRes @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -12428,6 +12431,7 @@
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
+ method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders();
method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12455,6 +12459,8 @@
method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader);
+ method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>);
method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
field @AnyRes public static final int ID_NULL = 0; // 0x0
}
@@ -12522,6 +12528,33 @@
}
+package android.content.res.loader {
+
+ public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader {
+ ctor public DirectoryResourceLoader(@NonNull java.io.File);
+ method @Nullable public java.io.File findFile(@NonNull String);
+ method @NonNull public java.io.File getDirectory();
+ }
+
+ public interface ResourceLoader {
+ method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
+ method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException;
+ method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme);
+ method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int);
+ }
+
+ public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
+ method public void close();
+ method @NonNull public static android.content.res.loader.ResourcesProvider empty();
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
+ }
+
+}
+
package android.database {
public abstract class AbstractCursor implements android.database.CrossProcessCursor {
@@ -24619,6 +24652,7 @@
field public static final int DolbyVisionLevelUhd30 = 64; // 0x40
field public static final int DolbyVisionLevelUhd48 = 128; // 0x80
field public static final int DolbyVisionLevelUhd60 = 256; // 0x100
+ field public static final int DolbyVisionProfileDvav110 = 1024; // 0x400
field public static final int DolbyVisionProfileDvavPen = 2; // 0x2
field public static final int DolbyVisionProfileDvavPer = 1; // 0x1
field public static final int DolbyVisionProfileDvavSe = 512; // 0x200
@@ -25415,6 +25449,9 @@
field public static final int METADATA_KEY_BITRATE = 20; // 0x14
field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
+ field public static final int METADATA_KEY_COLOR_RANGE = 37; // 0x25
+ field public static final int METADATA_KEY_COLOR_STANDARD = 35; // 0x23
+ field public static final int METADATA_KEY_COLOR_TRANSFER = 36; // 0x24
field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
field public static final int METADATA_KEY_DATE = 5; // 0x5
@@ -38245,6 +38282,7 @@
field public static final String COLUMN_MIME_TYPE = "mime_type";
field public static final String COLUMN_SIZE = "_size";
field public static final String COLUMN_SUMMARY = "summary";
+ field public static final int FLAG_DIR_BLOCKS_TREE = 32768; // 0x8000
field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -38463,16 +38501,17 @@
public static final class MediaStore.Audio {
ctor public MediaStore.Audio();
- method public static String keyFor(String);
+ method @Deprecated @Nullable public static String keyFor(@Nullable String);
}
public static interface MediaStore.Audio.AlbumColumns {
field public static final String ALBUM = "album";
field @Deprecated public static final String ALBUM_ART = "album_art";
field public static final String ALBUM_ID = "album_id";
- field public static final String ALBUM_KEY = "album_key";
+ field @Deprecated public static final String ALBUM_KEY = "album_key";
field public static final String ARTIST = "artist";
field public static final String ARTIST_ID = "artist_id";
+ field @Deprecated public static final String ARTIST_KEY = "artist_key";
field public static final String FIRST_YEAR = "minyear";
field public static final String LAST_YEAR = "maxyear";
field public static final String NUMBER_OF_SONGS = "numsongs";
@@ -38491,7 +38530,7 @@
public static interface MediaStore.Audio.ArtistColumns {
field public static final String ARTIST = "artist";
- field public static final String ARTIST_KEY = "artist_key";
+ field @Deprecated public static final String ARTIST_KEY = "artist_key";
field public static final String NUMBER_OF_ALBUMS = "number_of_albums";
field public static final String NUMBER_OF_TRACKS = "number_of_tracks";
}
@@ -38514,19 +38553,23 @@
public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
field public static final String ALBUM = "album";
field public static final String ALBUM_ID = "album_id";
- field public static final String ALBUM_KEY = "album_key";
+ field @Deprecated public static final String ALBUM_KEY = "album_key";
field public static final String ARTIST = "artist";
field public static final String ARTIST_ID = "artist_id";
- field public static final String ARTIST_KEY = "artist_key";
+ field @Deprecated public static final String ARTIST_KEY = "artist_key";
field public static final String BOOKMARK = "bookmark";
field public static final String COMPOSER = "composer";
+ field public static final String GENRE = "genre";
+ field public static final String GENRE_ID = "genre_id";
+ field @Deprecated public static final String GENRE_KEY = "genre_key";
field public static final String IS_ALARM = "is_alarm";
field public static final String IS_AUDIOBOOK = "is_audiobook";
field public static final String IS_MUSIC = "is_music";
field public static final String IS_NOTIFICATION = "is_notification";
field public static final String IS_PODCAST = "is_podcast";
field public static final String IS_RINGTONE = "is_ringtone";
- field public static final String TITLE_KEY = "title_key";
+ field @Deprecated public static final String TITLE_KEY = "title_key";
+ field public static final String TITLE_RESOURCE_URI = "title_resource_uri";
field public static final String TRACK = "track";
field public static final String YEAR = "year";
}
@@ -38752,6 +38795,9 @@
field public static final String ARTIST = "artist";
field public static final String BOOKMARK = "bookmark";
field public static final String CATEGORY = "category";
+ field public static final String COLOR_RANGE = "color_range";
+ field public static final String COLOR_STANDARD = "color_standard";
+ field public static final String COLOR_TRANSFER = "color_transfer";
field public static final String DESCRIPTION = "description";
field public static final String IS_PRIVATE = "isprivate";
field public static final String LANGUAGE = "language";
@@ -40810,7 +40856,7 @@
field public static final String EXTRA_KEY_ALIAS = "android.security.extra.KEY_ALIAS";
field public static final String EXTRA_NAME = "name";
field public static final String EXTRA_PKCS12 = "PKCS12";
- field public static final String KEY_ALIAS_SELECTION_DENIED = "alias-selection-denied-ef829e15-210a-409d-96c9-ee684fc41972";
+ field public static final String KEY_ALIAS_SELECTION_DENIED = "android:alias-selection-denied";
}
public interface KeyChainAliasCallback {
@@ -41871,6 +41917,7 @@
field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
field public static final String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE";
+ field public static final String META_DATA_BOOLEAN_TILE = "android.service.quicksettings.BOOLEAN_TILE";
}
}
@@ -44212,6 +44259,7 @@
field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
+ field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
field public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
field public static final String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -44557,6 +44605,7 @@
method public int describeContents();
method public int getAsuLevel();
method public int getDbm();
+ method public int getEcNo();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
@@ -45062,6 +45111,7 @@
method public long getDataLimitBytes();
method public long getDataUsageBytes();
method public long getDataUsageTime();
+ method @Nullable public int[] getNetworkTypes();
method @Nullable public CharSequence getSummary();
method @Nullable public CharSequence getTitle();
method public void writeToParcel(android.os.Parcel, int);
@@ -45081,6 +45131,7 @@
method public static android.telephony.SubscriptionPlan.Builder createRecurring(java.time.ZonedDateTime, java.time.Period);
method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
+ method @NonNull public android.telephony.SubscriptionPlan.Builder setNetworkTypes(@Nullable int[]);
method public android.telephony.SubscriptionPlan.Builder setSummary(@Nullable CharSequence);
method public android.telephony.SubscriptionPlan.Builder setTitle(@Nullable CharSequence);
}
@@ -45090,6 +45141,7 @@
method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
method public android.telephony.TelephonyManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
+ method public int getActiveModemCount();
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
method public int getCallState();
method public int getCardIdForDefaultEuicc();
@@ -45122,7 +45174,7 @@
method public String getNetworkOperatorName();
method public String getNetworkSpecifier();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
- method public int getPhoneCount();
+ method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
@@ -45138,6 +45190,7 @@
method public int getSimState();
method public int getSimState(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getSubscriberId();
+ method public int getSupportedModemCount();
method @Nullable public String getTypeAllocationCode();
method @Nullable public String getTypeAllocationCode(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @NonNull public java.util.List<android.telephony.UiccCardInfo> getUiccCardsInfo();
@@ -45242,6 +45295,10 @@
field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.extra.SUBSCRIPTION_ID";
field public static final String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
field public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
+ field public static final int MODEM_COUNT_DUAL_MODEM = 2; // 0x2
+ field public static final int MODEM_COUNT_NO_MODEM = 0; // 0x0
+ field public static final int MODEM_COUNT_SINGLE_MODEM = 1; // 0x1
+ field public static final int MODEM_COUNT_TRI_MODEM = 3; // 0x3
field public static final int MULTISIM_ALLOWED = 0; // 0x0
field public static final int MULTISIM_NOT_SUPPORTED_BY_CARRIER = 2; // 0x2
field public static final int MULTISIM_NOT_SUPPORTED_BY_HARDWARE = 1; // 0x1
diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt
new file mode 100644
index 0000000..2ca8cf4
--- /dev/null
+++ b/api/lint-baseline.txt
@@ -0,0 +1,1179 @@
+// Baseline format: 1.0
+AcronymName: android.system.ErrnoException#rethrowAsIOException():
+
+
+
+ActionValue: android.provider.Settings#ACTION_CONDITION_PROVIDER_SETTINGS:
+
+
+
+AllUpper: android.media.MediaCodecInfo.CodecCapabilities#FEATURE_LowLatency:
+
+
+
+ArrayReturn: android.app.Notification.MessagingStyle.Message#getMessagesFromBundleArray(android.os.Parcelable[]) parameter #0:
+
+ArrayReturn: android.content.ContentProviderOperation#resolveExtrasBackReferences(android.content.ContentProviderResult[], int) parameter #0:
+
+
+
+BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
+
+BroadcastBehavior: android.app.admin.DevicePolicyManager#ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED:
+
+BroadcastBehavior: android.app.admin.DevicePolicyManager#ACTION_MANAGED_PROFILE_PROVISIONED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_FINISHED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_STARTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_LOCAL_NAME_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_SCAN_MODE_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECT_REQUESTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_BOND_STATE_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_CLASS_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_FOUND:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_NAME_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_PAIRING_REQUEST:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_UUID:
+
+BroadcastBehavior: android.content.Intent#ACTION_AIRPLANE_MODE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_APPLICATION_RESTRICTIONS_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_LOW:
+
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_OKAY:
+
+BroadcastBehavior: android.content.Intent#ACTION_CAMERA_BUTTON:
+
+BroadcastBehavior: android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS:
+
+BroadcastBehavior: android.content.Intent#ACTION_CONFIGURATION_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_DATE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_DEVICE_STORAGE_LOW:
+
+BroadcastBehavior: android.content.Intent#ACTION_DEVICE_STORAGE_OK:
+
+BroadcastBehavior: android.content.Intent#ACTION_DOCK_EVENT:
+
+BroadcastBehavior: android.content.Intent#ACTION_DREAMING_STARTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_DREAMING_STOPPED:
+
+BroadcastBehavior: android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+
+BroadcastBehavior: android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+
+BroadcastBehavior: android.content.Intent#ACTION_GTALK_SERVICE_CONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_GTALK_SERVICE_DISCONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_HEADSET_PLUG:
+
+BroadcastBehavior: android.content.Intent#ACTION_INPUT_METHOD_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_LOCALE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MANAGE_PACKAGE_STORAGE:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_BAD_REMOVAL:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_BUTTON:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_CHECKING:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_EJECT:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_MOUNTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_NOFS:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_FINISHED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_SCAN_FILE:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_STARTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SHARED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_UNMOUNTABLE:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_UNMOUNTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_REPLACED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_SUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_UNSUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_NEW_OUTGOING_CALL:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGES_SUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGES_UNSUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_ADDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_DATA_CLEARED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_FIRST_LAUNCH:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_FULLY_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_INSTALL:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_NEEDS_VERIFICATION:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_REPLACED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_RESTARTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_VERIFIED:
+
+BroadcastBehavior: android.content.Intent#ACTION_POWER_CONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_POWER_DISCONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PROVIDER_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_REBOOT:
+
+BroadcastBehavior: android.content.Intent#ACTION_SCREEN_OFF:
+
+BroadcastBehavior: android.content.Intent#ACTION_SCREEN_ON:
+
+BroadcastBehavior: android.content.Intent#ACTION_SHUTDOWN:
+
+BroadcastBehavior: android.content.Intent#ACTION_TIMEZONE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_TIME_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_TIME_TICK:
+
+BroadcastBehavior: android.content.Intent#ACTION_UID_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_UMS_CONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_UMS_DISCONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_USER_PRESENT:
+
+BroadcastBehavior: android.content.Intent#ACTION_USER_UNLOCKED:
+
+BroadcastBehavior: android.content.Intent#ACTION_WALLPAPER_CHANGED:
+
+BroadcastBehavior: android.content.pm.PackageInstaller#ACTION_SESSION_COMMITTED:
+
+BroadcastBehavior: android.content.pm.PackageInstaller#ACTION_SESSION_UPDATED:
+
+BroadcastBehavior: android.hardware.Camera#ACTION_NEW_PICTURE:
+
+BroadcastBehavior: android.hardware.Camera#ACTION_NEW_VIDEO:
+
+BroadcastBehavior: android.hardware.input.InputManager#ACTION_QUERY_KEYBOARD_LAYOUTS:
+
+BroadcastBehavior: android.hardware.usb.UsbManager#ACTION_USB_ACCESSORY_DETACHED:
+
+BroadcastBehavior: android.hardware.usb.UsbManager#ACTION_USB_DEVICE_DETACHED:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_HDMI_AUDIO_PLUG:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_HEADSET_PLUG:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_MICROPHONE_MUTE_CHANGED:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_SPEAKERPHONE_STATE_CHANGED:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_INITIALIZE_PROGRAMS:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED:
+
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+
+BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
+
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+
+BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
+
+BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#DATA_SMS_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SECRET_CODE_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SIM_FULL_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_REJECTED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#WAP_PUSH_DELIVER_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#WAP_PUSH_RECEIVED_ACTION:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_KEYCHAIN_CHANGED:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_KEY_ACCESS_CHANGED:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_STORAGE_CHANGED:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_TRUST_STORE_CHANGED:
+
+BroadcastBehavior: android.speech.tts.TextToSpeech#ACTION_TTS_QUEUE_PROCESSING_COMPLETED:
+
+BroadcastBehavior: android.speech.tts.TextToSpeech.Engine#ACTION_TTS_DATA_INSTALLED:
+
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED:
+
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_DEFAULT_SUBSCRIPTION_CHANGED:
+
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_REFRESH_SUBSCRIPTION_PLANS:
+
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SECRET_CODE:
+
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED:
+
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED:
+
+BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE:
+
+
+
+CompileTimeConstant: android.icu.util.JapaneseCalendar#REIWA:
+
+
+
+DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle):
+
+DeprecationMismatch: android.app.Activity#enterPictureInPictureMode():
+
+DeprecationMismatch: android.app.Instrumentation#startAllocCounting():
+
+DeprecationMismatch: android.app.Instrumentation#stopAllocCounting():
+
+DeprecationMismatch: android.app.Notification#bigContentView:
+
+DeprecationMismatch: android.app.Notification#contentView:
+
+DeprecationMismatch: android.app.Notification#headsUpContentView:
+
+DeprecationMismatch: android.app.Notification#tickerView:
+
+DeprecationMismatch: android.app.Notification.Action.Builder#Builder(int, CharSequence, android.app.PendingIntent):
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getCancelLabel():
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getConfirmLabel():
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getInProgressLabel():
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setCancelLabel(CharSequence):
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setConfirmLabel(CharSequence):
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setInProgressLabel(CharSequence):
+
+DeprecationMismatch: android.app.Notification.Builder#setContent(android.widget.RemoteViews):
+
+DeprecationMismatch: android.app.Notification.Builder#setTicker(CharSequence, android.widget.RemoteViews):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getContentIcon():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getContentIconGravity():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getCustomContentHeight():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getCustomSizePreset():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getGravity():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintAvoidBackgroundClipping():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintHideIcon():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintScreenTimeout():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintShowBackgroundOnly():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setContentIcon(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setContentIconGravity(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setCustomContentHeight(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setCustomSizePreset(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setGravity(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintAvoidBackgroundClipping(boolean):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintHideIcon(boolean):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintScreenTimeout(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintShowBackgroundOnly(boolean):
+
+DeprecationMismatch: android.content.ContextWrapper#clearWallpaper():
+
+DeprecationMismatch: android.content.ContextWrapper#getWallpaper():
+
+DeprecationMismatch: android.content.ContextWrapper#getWallpaperDesiredMinimumHeight():
+
+DeprecationMismatch: android.content.ContextWrapper#getWallpaperDesiredMinimumWidth():
+
+DeprecationMismatch: android.content.ContextWrapper#peekWallpaper():
+
+DeprecationMismatch: android.content.ContextWrapper#removeStickyBroadcast(android.content.Intent):
+
+DeprecationMismatch: android.content.ContextWrapper#removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyBroadcast(android.content.Intent):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
+
+DeprecationMismatch: android.content.ContextWrapper#setWallpaper(android.graphics.Bitmap):
+
+DeprecationMismatch: android.content.ContextWrapper#setWallpaper(java.io.InputStream):
+
+DeprecationMismatch: android.database.CursorWrapper#deactivate():
+
+DeprecationMismatch: android.database.CursorWrapper#requery():
+
+DeprecationMismatch: android.graphics.ComposeShader#ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode):
+
+DeprecationMismatch: android.graphics.PixelFormat#A_8:
+
+DeprecationMismatch: android.graphics.PixelFormat#LA_88:
+
+DeprecationMismatch: android.graphics.PixelFormat#L_8:
+
+DeprecationMismatch: android.graphics.PixelFormat#RGBA_4444:
+
+DeprecationMismatch: android.graphics.PixelFormat#RGBA_5551:
+
+DeprecationMismatch: android.graphics.PixelFormat#RGB_332:
+
+DeprecationMismatch: android.net.wifi.WifiManager#EXTRA_BSSID:
+
+DeprecationMismatch: android.net.wifi.WifiManager#EXTRA_WIFI_INFO:
+
+DeprecationMismatch: android.opengl.EGL14#eglCreatePixmapSurface(android.opengl.EGLDisplay, android.opengl.EGLConfig, int, int[], int):
+
+DeprecationMismatch: android.opengl.GLES20#GL_STENCIL_INDEX:
+
+DeprecationMismatch: android.opengl.GLSurfaceView#surfaceRedrawNeeded(android.view.SurfaceHolder):
+
+DeprecationMismatch: android.os.UserManager#setUserRestrictions(android.os.Bundle):
+
+DeprecationMismatch: android.os.UserManager#setUserRestrictions(android.os.Bundle, android.os.UserHandle):
+
+DeprecationMismatch: android.provider.Contacts.People#markAsContacted(android.content.ContentResolver, long):
+
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_X:
+
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_Y:
+
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_Z:
+
+DeprecationMismatch: android.speech.tts.TextToSpeech#areDefaultsEnforced():
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_12HOUR:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_24HOUR:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_AMPM:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_MIDNIGHT:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_NOON:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_NOON_MIDNIGHT:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_NO_NOON_MIDNIGHT:
+
+DeprecationMismatch: android.view.ViewGroup.LayoutParams#FILL_PARENT:
+
+DeprecationMismatch: android.view.Window#setTitleColor(int):
+
+DeprecationMismatch: android.view.accessibility.AccessibilityEvent#MAX_TEXT_LENGTH:
+
+DeprecationMismatch: android.webkit.WebSettings#getSaveFormData():
+
+DeprecationMismatch: android.webkit.WebView#shouldDelayChildPressedState():
+
+DeprecationMismatch: android.webkit.WebViewDatabase#clearFormData():
+
+DeprecationMismatch: android.webkit.WebViewDatabase#hasFormData():
+
+DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]):
+
+
+
+GenericException: android.content.res.loader.ResourcesProvider#finalize():
+ Methods must not throw generic exceptions (`java.lang.Throwable`)
+
+
+HiddenSuperclass: android.content.res.ColorStateList:
+
+HiddenSuperclass: android.graphics.Canvas:
+
+HiddenSuperclass: android.graphics.RecordingCanvas:
+
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.AuthenticationCallback:
+
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.AuthenticationResult:
+
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.CryptoObject:
+
+HiddenSuperclass: android.hardware.fingerprint.FingerprintManager.AuthenticationCallback:
+
+HiddenSuperclass: android.hardware.fingerprint.FingerprintManager.CryptoObject:
+
+HiddenSuperclass: android.media.AudioTrack:
+
+HiddenSuperclass: android.media.MediaPlayer:
+
+HiddenSuperclass: android.media.SoundPool:
+
+HiddenSuperclass: android.service.autofill.CharSequenceTransformation:
+
+HiddenSuperclass: android.service.autofill.DateTransformation:
+
+HiddenSuperclass: android.service.autofill.DateValueSanitizer:
+
+HiddenSuperclass: android.service.autofill.ImageTransformation:
+
+HiddenSuperclass: android.service.autofill.LuhnChecksumValidator:
+
+HiddenSuperclass: android.service.autofill.RegexValidator:
+
+HiddenSuperclass: android.service.autofill.TextValueSanitizer:
+
+HiddenSuperclass: android.service.autofill.VisibilitySetterAction:
+
+HiddenSuperclass: android.util.StatsLog:
+
+
+
+MissingNullability: android.app.AsyncNotedAppOp#equals(Object) parameter #0:
+
+MissingNullability: android.app.AsyncNotedAppOp#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.app.SyncNotedAppOp#equals(Object) parameter #0:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#ELYMAIC:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NANDINAGARI:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NYIAKENG_PUACHUE_HMONG:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#OTTOMAN_SIYAQ_NUMBERS:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SMALL_KANA_EXTENSION:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#TAMIL_SUPPLEMENT:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#WANCHO:
+
+MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth):
+
+MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth) parameter #1:
+
+MissingNullability: android.icu.util.VersionInfo#UNICODE_12_0:
+
+MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1:
+
+MissingNullability: android.media.MediaMetadataRetriever#getFrameAtTime(long, int, android.media.MediaMetadataRetriever.BitmapParams):
+
+MissingNullability: android.media.MediaMetadataRetriever#getScaledFrameAtTime(long, int, int, int, android.media.MediaMetadataRetriever.BitmapParams):
+
+
+
+RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
+
+RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+RequiresPermission: android.app.AlarmManager#setTime(long):
+
+RequiresPermission: android.app.AppOpsManager#isOpActive(String, int, String):
+
+RequiresPermission: android.app.AppOpsManager#startWatchingActive(String[], java.util.concurrent.Executor, android.app.AppOpsManager.OnOpActiveChangedListener):
+
+RequiresPermission: android.app.DownloadManager.Request#setDestinationInExternalPublicDir(String, String):
+
+RequiresPermission: android.app.DownloadManager.Request#setDestinationUri(android.net.Uri):
+
+RequiresPermission: android.app.DownloadManager.Request#setNotificationVisibility(int):
+
+RequiresPermission: android.app.DownloadManager.Request#setShowRunningNotification(boolean):
+
+RequiresPermission: android.app.Notification.Builder#setFullScreenIntent(android.app.PendingIntent, boolean):
+
+RequiresPermission: android.app.Service#startForeground(int, android.app.Notification):
+
+RequiresPermission: android.app.WallpaperInfo#getSettingsSliceUri():
+
+RequiresPermission: android.app.WallpaperManager#clear():
+
+RequiresPermission: android.app.WallpaperManager#clearWallpaper():
+
+RequiresPermission: android.app.WallpaperManager#setBitmap(android.graphics.Bitmap):
+
+RequiresPermission: android.app.WallpaperManager#setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean):
+
+RequiresPermission: android.app.WallpaperManager#setDisplayPadding(android.graphics.Rect):
+
+RequiresPermission: android.app.WallpaperManager#setResource(int):
+
+RequiresPermission: android.app.WallpaperManager#setStream(java.io.InputStream):
+
+RequiresPermission: android.app.WallpaperManager#setStream(java.io.InputStream, android.graphics.Rect, boolean):
+
+RequiresPermission: android.app.WallpaperManager#suggestDesiredDimensions(int, int):
+
+RequiresPermission: android.app.admin.DevicePolicyManager#bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle):
+
+RequiresPermission: android.app.admin.DevicePolicyManager#getPasswordComplexity():
+
+RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage(android.content.ComponentName, String, boolean):
+
+RequiresPermission: android.app.backup.BackupManager#dataChanged(String):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryExternalStatsForUser(java.util.UUID, android.os.UserHandle):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForPackage(java.util.UUID, String, android.os.UserHandle):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForUid(java.util.UUID, int):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForUser(java.util.UUID, android.os.UserHandle):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryAndAggregateUsageStats(long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryConfigurations(int, long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryEventStats(int, long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryEvents(long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryUsageStats(int, long, long):
+
+RequiresPermission: android.appwidget.AppWidgetManager#bindAppWidgetIdIfAllowed(int, android.os.UserHandle, android.content.ComponentName, android.os.Bundle):
+
+RequiresPermission: android.bluetooth.BluetoothA2dp#isA2dpPlaying(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothAdapter#getName():
+
+RequiresPermission: android.bluetooth.BluetoothDevice#setPin(byte[]):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#abortReliableWrite():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#beginReliableWrite():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#disconnect():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#discoverServices():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#executeReliableWrite():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#getService(java.util.UUID):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#getServices():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#readDescriptor(android.bluetooth.BluetoothGattDescriptor):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#readRemoteRssi():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#requestMtu(int):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#writeDescriptor(android.bluetooth.BluetoothGattDescriptor):
+
+RequiresPermission: android.bluetooth.BluetoothGattCharacteristic#BluetoothGattCharacteristic(java.util.UUID, int, int):
+
+RequiresPermission: android.bluetooth.BluetoothGattCharacteristic#addDescriptor(android.bluetooth.BluetoothGattDescriptor):
+
+RequiresPermission: android.bluetooth.BluetoothGattDescriptor#BluetoothGattDescriptor(java.util.UUID, int):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#addService(android.bluetooth.BluetoothGattService):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#cancelConnection(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#clearServices():
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#connect(android.bluetooth.BluetoothDevice, boolean):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#getService(java.util.UUID):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#getServices():
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#removeService(android.bluetooth.BluetoothGattService):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]):
+
+RequiresPermission: android.bluetooth.BluetoothGattService#BluetoothGattService(java.util.UUID, int):
+
+RequiresPermission: android.bluetooth.BluetoothGattService#addCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+
+RequiresPermission: android.bluetooth.BluetoothGattService#addService(android.bluetooth.BluetoothGattService):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#isAudioConnected(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#startVoiceRecognition(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#stopVoiceRecognition(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getConnectedDevices():
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getConnectionState(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getDevicesMatchingConnectionStates(int[]):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration):
+
+RequiresPermission: android.bluetooth.le.AdvertisingSet#enableAdvertising(boolean, int, int):
+
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback):
+
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback):
+
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#stopAdvertising(android.bluetooth.le.AdvertiseCallback):
+
+RequiresPermission: android.companion.CompanionDeviceManager#associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler):
+
+RequiresPermission: android.content.ContentResolver#addPeriodicSync(android.accounts.Account, String, android.os.Bundle, long):
+
+RequiresPermission: android.content.ContentResolver#cancelSync(android.content.SyncRequest):
+
+RequiresPermission: android.content.ContentResolver#getCurrentSync():
+
+RequiresPermission: android.content.ContentResolver#getCurrentSyncs():
+
+RequiresPermission: android.content.ContentResolver#getIsSyncable(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#getMasterSyncAutomatically():
+
+RequiresPermission: android.content.ContentResolver#getPeriodicSyncs(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#getSyncAutomatically(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#isSyncActive(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#isSyncPending(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#removePeriodicSync(android.accounts.Account, String, android.os.Bundle):
+
+RequiresPermission: android.content.ContentResolver#setIsSyncable(android.accounts.Account, String, int):
+
+RequiresPermission: android.content.ContentResolver#setMasterSyncAutomatically(boolean):
+
+RequiresPermission: android.content.ContentResolver#setSyncAutomatically(android.accounts.Account, String, boolean):
+
+RequiresPermission: android.content.Context#clearWallpaper():
+
+RequiresPermission: android.content.Context#getExternalCacheDir():
+
+RequiresPermission: android.content.Context#getExternalCacheDirs():
+
+RequiresPermission: android.content.Context#getExternalFilesDir(String):
+
+RequiresPermission: android.content.Context#getExternalFilesDirs(String):
+
+RequiresPermission: android.content.Context#getExternalMediaDirs():
+
+RequiresPermission: android.content.Context#getObbDir():
+
+RequiresPermission: android.content.Context#getObbDirs():
+
+RequiresPermission: android.content.Context#removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+
+RequiresPermission: android.content.Context#setWallpaper(android.graphics.Bitmap):
+
+RequiresPermission: android.content.Context#setWallpaper(java.io.InputStream):
+
+RequiresPermission: android.content.pm.LauncherApps.Callback#onPackagesSuspended(String[], android.os.UserHandle, android.os.Bundle):
+
+RequiresPermission: android.content.pm.PackageManager#canRequestPackageInstalls():
+
+RequiresPermission: android.content.pm.PackageManager#getSuspendedPackageAppExtras():
+
+RequiresPermission: android.content.pm.PackageManager#isPackageSuspended():
+
+RequiresPermission: android.hardware.camera2.CameraCharacteristics#getKeysNeedingPermission():
+
+RequiresPermission: android.hardware.usb.UsbManager#hasPermission(android.hardware.usb.UsbDevice):
+
+RequiresPermission: android.hardware.usb.UsbManager#requestPermission(android.hardware.usb.UsbDevice, android.app.PendingIntent):
+
+RequiresPermission: android.location.LocationManager#addGpsStatusListener(android.location.GpsStatus.Listener):
+
+RequiresPermission: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener):
+
+RequiresPermission: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
+
+RequiresPermission: android.location.LocationManager#addNmeaListener(java.util.concurrent.Executor, android.location.OnNmeaMessageListener):
+
+RequiresPermission: android.location.LocationManager#addProximityAlert(double, double, float, long, android.app.PendingIntent):
+
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(android.location.GnssStatus.Callback):
+
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(android.location.GnssStatus.Callback, android.os.Handler):
+
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+
+RequiresPermission: android.media.AudioManager#startBluetoothSco():
+
+RequiresPermission: android.media.AudioManager#stopBluetoothSco():
+
+RequiresPermission: android.media.MediaExtractor#setDataSource(String):
+
+RequiresPermission: android.media.MediaExtractor#setDataSource(String, java.util.Map<java.lang.String,java.lang.String>):
+
+RequiresPermission: android.media.MediaExtractor#setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>):
+
+RequiresPermission: android.media.MediaPlayer#setWakeMode(android.content.Context, int):
+
+RequiresPermission: android.media.MediaSession2Service#onUpdateNotification(android.media.MediaSession2):
+
+RequiresPermission: android.media.RingtoneManager#getCursor():
+
+RequiresPermission: android.media.RingtoneManager#getValidRingtoneUri(android.content.Context):
+
+RequiresPermission: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
+
+RequiresPermission: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
+
+RequiresPermission: android.media.session.MediaSessionManager#getActiveSessions(android.content.ComponentName):
+
+RequiresPermission: android.media.session.MediaSessionManager#isTrustedForMediaControl(android.media.session.MediaSessionManager.RemoteUserInfo):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler, int):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, int):
+
+RequiresPermission: android.net.sip.SipAudioCall#setSpeakerMode(boolean):
+
+RequiresPermission: android.net.sip.SipAudioCall#startAudio():
+
+RequiresPermission: android.net.wifi.WifiManager#getScanResults():
+
+RequiresPermission: android.net.wifi.WifiManager#setWifiEnabled(boolean):
+
+RequiresPermission: android.net.wifi.WifiManager#startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler):
+
+RequiresPermission: android.net.wifi.WifiManager#startScan():
+
+RequiresPermission: android.net.wifi.aware.IdentityChangedListener#onIdentityChanged(byte[]):
+
+RequiresPermission: android.net.wifi.aware.WifiAwareManager#attach(android.net.wifi.aware.AttachCallback, android.net.wifi.aware.IdentityChangedListener, android.os.Handler):
+
+RequiresPermission: android.net.wifi.aware.WifiAwareSession#publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler):
+
+RequiresPermission: android.net.wifi.aware.WifiAwareSession#subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler):
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundNdefPush(android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.NfcAdapter#setBeamPushUris(android.net.Uri[], android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...):
+
+RequiresPermission: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
+
+RequiresPermission: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
+
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+
+RequiresPermission: android.os.Build#getSerial():
+
+RequiresPermission: android.os.Debug#dumpService(String, java.io.FileDescriptor, String[]):
+
+RequiresPermission: android.os.Environment#getExternalStorageDirectory():
+
+RequiresPermission: android.os.PowerManager#newWakeLock(int, String):
+
+RequiresPermission: android.os.PowerManager#reboot(String):
+
+RequiresPermission: android.os.RecoverySystem#rebootWipeUserData(android.content.Context):
+
+RequiresPermission: android.os.StrictMode.VmPolicy.Builder#detectFileUriExposure():
+
+RequiresPermission: android.os.UserManager#getUserName():
+
+RequiresPermission: android.os.UserManager#isUserUnlocked(android.os.UserHandle):
+
+RequiresPermission: android.os.health.SystemHealthManager#takeUidSnapshot(int):
+
+RequiresPermission: android.os.health.SystemHealthManager#takeUidSnapshots(int[]):
+
+RequiresPermission: android.os.storage.StorageVolume#createAccessIntent(String):
+
+RequiresPermission: android.provider.MediaStore#setRequireOriginal(android.net.Uri):
+
+RequiresPermission: android.provider.Settings#canDrawOverlays(android.content.Context):
+
+RequiresPermission: android.provider.Settings.System#canWrite(android.content.Context):
+
+RequiresPermission: android.telecom.TelecomManager#acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#acceptRingingCall():
+
+RequiresPermission: android.telecom.TelecomManager#acceptRingingCall(int):
+
+RequiresPermission: android.telecom.TelecomManager#addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle):
+
+RequiresPermission: android.telecom.TelecomManager#cancelMissedCallsNotification():
+
+RequiresPermission: android.telecom.TelecomManager#endCall():
+
+RequiresPermission: android.telecom.TelecomManager#getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#getCallCapablePhoneAccounts():
+
+RequiresPermission: android.telecom.TelecomManager#getDefaultOutgoingPhoneAccount(String):
+
+RequiresPermission: android.telecom.TelecomManager#getLine1Number(android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#getSelfManagedPhoneAccounts():
+
+RequiresPermission: android.telecom.TelecomManager#getVoiceMailNumber(android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#handleMmi(String):
+
+RequiresPermission: android.telecom.TelecomManager#handleMmi(String, android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#isInCall():
+
+RequiresPermission: android.telecom.TelecomManager#isInManagedCall():
+
+RequiresPermission: android.telecom.TelecomManager#isVoiceMailNumber(android.telecom.PhoneAccountHandle, String):
+
+RequiresPermission: android.telecom.TelecomManager#placeCall(android.net.Uri, android.os.Bundle):
+
+RequiresPermission: android.telecom.TelecomManager#showInCallScreen(boolean):
+
+RequiresPermission: android.telecom.TelecomManager#silenceRinger():
+
+RequiresPermission: android.telephony.CarrierConfigManager#getConfig():
+
+RequiresPermission: android.telephony.CarrierConfigManager#getConfigByComponentForSubId(String, int):
+
+RequiresPermission: android.telephony.CarrierConfigManager#getConfigForSubId(int):
+
+RequiresPermission: android.telephony.PhoneStateListener#onCallStateChanged(int, String):
+
+RequiresPermission: android.telephony.SmsManager#injectSmsPdu(byte[], String, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SmsManager#sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SmsManager#sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>):
+
+RequiresPermission: android.telephony.SmsManager#sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SmsManager#sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SubscriptionManager#addSubscriptionsIntoGroup(java.util.List<java.lang.Integer>, android.os.ParcelUuid):
+
+RequiresPermission: android.telephony.SubscriptionManager#createSubscriptionGroup(java.util.List<java.lang.Integer>):
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfo(int):
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoCount():
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoForSimSlotIndex(int):
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoList():
+
+RequiresPermission: android.telephony.SubscriptionManager#getOpportunisticSubscriptions():
+
+RequiresPermission: android.telephony.SubscriptionManager#getSubscriptionsInGroup(android.os.ParcelUuid):
+
+RequiresPermission: android.telephony.SubscriptionManager#removeSubscriptionsFromGroup(java.util.List<java.lang.Integer>, android.os.ParcelUuid):
+
+RequiresPermission: android.telephony.SubscriptionManager#setOpportunistic(boolean, int):
+
+RequiresPermission: android.telephony.TelephonyManager#doesSwitchMultiSimConfigTriggerReboot():
+
+RequiresPermission: android.telephony.TelephonyManager#getCarrierConfig():
+
+RequiresPermission: android.telephony.TelephonyManager#getDataNetworkType():
+
+RequiresPermission: android.telephony.TelephonyManager#getDeviceId():
+
+RequiresPermission: android.telephony.TelephonyManager#getDeviceId(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getDeviceSoftwareVersion():
+
+RequiresPermission: android.telephony.TelephonyManager#getEmergencyNumberList():
+
+RequiresPermission: android.telephony.TelephonyManager#getEmergencyNumberList(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getForbiddenPlmns():
+
+RequiresPermission: android.telephony.TelephonyManager#getGroupIdLevel1():
+
+RequiresPermission: android.telephony.TelephonyManager#getImei(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getLine1Number():
+
+RequiresPermission: android.telephony.TelephonyManager#getMeid():
+
+RequiresPermission: android.telephony.TelephonyManager#getMeid(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getNai():
+
+RequiresPermission: android.telephony.TelephonyManager#getPreferredOpportunisticDataSubscription():
+
+RequiresPermission: android.telephony.TelephonyManager#getServiceState():
+
+RequiresPermission: android.telephony.TelephonyManager#getSimSerialNumber():
+
+RequiresPermission: android.telephony.TelephonyManager#getSubscriberId():
+
+RequiresPermission: android.telephony.TelephonyManager#getVisualVoicemailPackageName():
+
+RequiresPermission: android.telephony.TelephonyManager#getVoiceMailAlphaTag():
+
+RequiresPermission: android.telephony.TelephonyManager#getVoiceMailNumber():
+
+RequiresPermission: android.telephony.TelephonyManager#getVoiceNetworkType():
+
+RequiresPermission: android.telephony.TelephonyManager#iccCloseLogicalChannel(int):
+
+RequiresPermission: android.telephony.TelephonyManager#iccExchangeSimIO(int, int, int, int, int, String):
+
+RequiresPermission: android.telephony.TelephonyManager#iccOpenLogicalChannel(String):
+
+RequiresPermission: android.telephony.TelephonyManager#iccOpenLogicalChannel(String, int):
+
+RequiresPermission: android.telephony.TelephonyManager#iccTransmitApduBasicChannel(int, int, int, int, int, String):
+
+RequiresPermission: android.telephony.TelephonyManager#iccTransmitApduLogicalChannel(int, int, int, int, int, int, String):
+
+RequiresPermission: android.telephony.TelephonyManager#isDataEnabled():
+
+RequiresPermission: android.telephony.TelephonyManager#isDataRoamingEnabled():
+
+RequiresPermission: android.telephony.TelephonyManager#isMultiSimSupported():
+
+RequiresPermission: android.telephony.TelephonyManager#requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback):
+
+RequiresPermission: android.telephony.TelephonyManager#sendEnvelopeWithStatus(String):
+
+RequiresPermission: android.telephony.TelephonyManager#sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler):
+
+RequiresPermission: android.telephony.TelephonyManager#sendVisualVoicemailSms(String, int, String, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.TelephonyManager#setDataEnabled(boolean):
+
+RequiresPermission: android.telephony.TelephonyManager#setNetworkSelectionModeAutomatic():
+
+RequiresPermission: android.telephony.TelephonyManager#setNetworkSelectionModeManual(String, boolean):
+
+RequiresPermission: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+RequiresPermission: android.telephony.TelephonyManager#setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri):
+
+RequiresPermission: android.telephony.TelephonyManager#setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean):
+
+RequiresPermission: android.telephony.TelephonyManager#switchMultiSimConfig(int):
+
+RequiresPermission: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#deleteSubscription(int, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#switchToSubscription(int, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#updateSubscriptionNickname(int, String, android.app.PendingIntent):
+
+RequiresPermission: android.view.inputmethod.InputMethodManager#setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype):
+
+RequiresPermission: android.view.inputmethod.InputMethodManager#setInputMethod(android.os.IBinder, String):
+
+RequiresPermission: android.view.inputmethod.InputMethodManager#setInputMethodAndSubtype(android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype):
+
+RequiresPermission: android.webkit.WebSettings#setBlockNetworkLoads(boolean):
+
+RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean):
+
+
+
+SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
+
+
+
+StreamFiles: android.content.res.loader.DirectoryResourceLoader#DirectoryResourceLoader(java.io.File):
+ Methods accepting `File` should also accept `FileDescriptor` or streams: constructor android.content.res.loader.DirectoryResourceLoader(java.io.File)
+
+
+Todo: android.hardware.camera2.params.StreamConfigurationMap:
+
+Todo: android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration(Class<T>, android.util.Size):
+
+Todo: android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration(int, android.util.Size):
+
+Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor):
+
+Todo: android.telephony.CarrierConfigManager#KEY_USE_OTASP_FOR_PROVISIONING_BOOL:
+
diff --git a/api/system-current.txt b/api/system-current.txt
index 447ba30..6e35200 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -172,6 +172,7 @@
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+ field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
@@ -1359,7 +1360,7 @@
public abstract class Context {
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
- method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle);
+ method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method public abstract android.content.Context createCredentialProtectedStorageContext();
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract java.io.File getPreloadsFileCache();
@@ -1754,7 +1755,9 @@
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+ field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
+ field public static final int PROTECTION_FLAG_WIFI = 8388608; // 0x800000
field @Nullable public final String backgroundPermission;
field @StringRes public int requestRes;
}
@@ -3420,7 +3423,9 @@
public class Location implements android.os.Parcelable {
method public boolean isComplete();
method public void makeComplete();
+ method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
method public void setIsFromMockProvider(boolean);
+ field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
@@ -5697,7 +5702,10 @@
package android.permission {
public final class PermissionControllerManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
@@ -5711,17 +5719,19 @@
public abstract class PermissionControllerService extends android.app.Service {
ctor public PermissionControllerService();
+ method @BinderThread public void onApplyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @NonNull public final android.os.IBinder onBind(android.content.Intent);
method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer);
method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>);
method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>);
method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable);
method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
- method @BinderThread public abstract void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @BinderThread public abstract void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
+ method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public void onUpdateUserSensitive();
field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
}
@@ -7312,7 +7322,7 @@
public abstract class CellBroadcastService extends android.app.Service {
ctor public CellBroadcastService();
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
- method public abstract void onCdmaCellBroadcastSms(int, byte[]);
+ method public abstract void onCdmaCellBroadcastSms(int, byte[], int);
method public abstract void onGsmCellBroadcastSms(int, byte[]);
field public static final String CELL_BROADCAST_SERVICE_INTERFACE = "android.telephony.CellBroadcastService";
}
@@ -9576,17 +9586,17 @@
public class ImsSmsImplBase {
ctor public ImsSmsImplBase();
- method public void acknowledgeSms(int, int, int);
- method public void acknowledgeSmsReport(int, int, int);
+ method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int);
+ method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
method public String getSmsFormat();
method public void onReady();
- method @Deprecated public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
- method public final void onSendSmsResultError(int, int, int, int, int) throws java.lang.RuntimeException;
- method public final void onSendSmsResultSuccess(int, int) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSendSmsResult(int, @IntRange(from=0, to=65535) int, int, int) throws java.lang.RuntimeException;
+ method public final void onSendSmsResultError(int, @IntRange(from=0, to=65535) int, int, int, int) throws java.lang.RuntimeException;
+ method public final void onSendSmsResultSuccess(int, @IntRange(from=0, to=65535) int) throws java.lang.RuntimeException;
method public final void onSmsReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method @Deprecated public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSmsStatusReportReceived(int, @IntRange(from=0, to=65535) int, String, byte[]) throws java.lang.RuntimeException;
method public final void onSmsStatusReportReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method public void sendSms(int, int, String, String, boolean, byte[]);
+ method public void sendSms(int, @IntRange(from=0, to=65535) int, String, String, boolean, byte[]);
field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
field public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4; // 0x4
@@ -9758,9 +9768,10 @@
method public final long getUserActivityTimeout();
method public final void setUserActivityTimeout(long);
field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
+ field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10
}
- @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
+ @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
}
}
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
new file mode 100644
index 0000000..57a853a
--- /dev/null
+++ b/api/system-lint-baseline.txt
@@ -0,0 +1,391 @@
+// Baseline format: 1.0
+ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
+
+
+
+ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
+
+
+
+GenericException: android.app.prediction.AppPredictor#finalize():
+
+GenericException: android.hardware.location.ContextHubClient#finalize():
+
+GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize():
+
+GenericException: android.service.autofill.augmented.FillWindow#finalize():
+
+
+
+InterfaceConstant: android.service.storage.ExternalStorageService#SERVICE_INTERFACE:
+
+
+
+KotlinKeyword: android.app.Notification#when:
+
+
+
+MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString():
+
+MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged(android.content.ComponentName) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged(android.media.session.MediaSession.Token) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName) parameter #1:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token) parameter #1:
+
+MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #0:
+
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
+
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
+
+MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
+
+MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
+
+MissingNullability: android.provider.ContactsContract.MetadataSync#CONTENT_URI:
+
+MissingNullability: android.provider.ContactsContract.MetadataSync#METADATA_AUTHORITY_URI:
+
+MissingNullability: android.provider.ContactsContract.MetadataSyncState#CONTENT_URI:
+
+MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #0:
+
+MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #1:
+
+MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
+
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
+
+MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
+
+MissingNullability: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallFurther(boolean):
+
+MissingNullability: android.telephony.CallerInfo#toString():
+
+MissingNullability: android.telephony.CellBroadcastService#onBind(android.content.Intent):
+
+MissingNullability: android.telephony.CellBroadcastService#onBind(android.content.Intent) parameter #0:
+
+MissingNullability: android.telephony.CellBroadcastService#onCdmaCellBroadcastSms(int, byte[]) parameter #1:
+
+MissingNullability: android.telephony.CellBroadcastService#onCdmaCellBroadcastSms(int, byte[], int) parameter #1:
+
+MissingNullability: android.telephony.CellBroadcastService#onGsmCellBroadcastSms(int, byte[]) parameter #1:
+
+MissingNullability: android.telephony.ModemActivityInfo#toString():
+
+MissingNullability: android.telephony.ModemActivityInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.ModemActivityInfo.TransmitPower#toString():
+
+MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.telephony.SmsCbCmasInfo#toString():
+
+MissingNullability: android.telephony.SmsCbCmasInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SmsCbEtwsInfo#toString():
+
+MissingNullability: android.telephony.SmsCbEtwsInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SmsCbLocation#equals(Object) parameter #0:
+
+MissingNullability: android.telephony.SmsCbLocation#toString():
+
+MissingNullability: android.telephony.SmsCbLocation#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SmsCbMessage#toString():
+
+MissingNullability: android.telephony.SmsCbMessage#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringDaily(java.time.ZonedDateTime) parameter #0:
+
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringMonthly(java.time.ZonedDateTime) parameter #0:
+
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringWeekly(java.time.ZonedDateTime) parameter #0:
+
+MissingNullability: android.telephony.cdma.CdmaSmsCbProgramData#toString():
+
+MissingNullability: android.telephony.cdma.CdmaSmsCbProgramData#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #1:
+
+MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #2:
+
+MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+
+MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0:
+
+
+
+NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+
+
+
+ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
+
+ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+
+ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
+
+
+
+SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
+
+SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[]):
+
+SamShouldBeLast: android.accounts.AccountManager#confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#renameAccount(android.accounts.Account, String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.app.AlarmManager#set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+
+SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+
+SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+
+SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
+
+SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.Context#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.ContextWrapper#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.ContextWrapper#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.IntentFilter#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.LauncherApps#registerPackageInstallerSessionCallback(java.util.concurrent.Executor, android.content.pm.PackageInstaller.SessionCallback):
+
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
+
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
+
+SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.AudioRecord#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+
+SamShouldBeLast: android.media.AudioRecordingMonitor#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+
+SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#registerCallback(java.util.concurrent.Executor, android.media.session.MediaSessionManager.Callback):
+
+SamShouldBeLast: android.net.ConnectivityManager#createSocketKeepalive(android.net.Network, android.net.IpSecManager.UdpEncapsulationSocket, java.net.InetAddress, java.net.InetAddress, java.util.concurrent.Executor, android.net.SocketKeepalive.Callback):
+
+SamShouldBeLast: android.net.wifi.rtt.WifiRttManager#startRanging(android.net.wifi.rtt.RangingRequest, java.util.concurrent.Executor, android.net.wifi.rtt.RangingResultCallback):
+
+SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
+
+SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
+
+SamShouldBeLast: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
+
+SamShouldBeLast: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
+
+SamShouldBeLast: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
+
+SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
+
+SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.Binder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.Handler#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.os.Handler#postAtTime(Runnable, Object, long):
+
+SamShouldBeLast: android.os.Handler#postAtTime(Runnable, long):
+
+SamShouldBeLast: android.os.Handler#postDelayed(Runnable, Object, long):
+
+SamShouldBeLast: android.os.Handler#postDelayed(Runnable, long):
+
+SamShouldBeLast: android.os.Handler#removeCallbacks(Runnable, Object):
+
+SamShouldBeLast: android.os.IBinder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.RecoverySystem#verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#addProgressListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadProgressListener):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#addStatusListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadStatusListener):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsDownloadSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsDownloadSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#create(android.content.Context, int, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#startGroupCall(long, java.util.List<java.lang.Integer>, java.util.List<java.lang.Integer>, java.util.concurrent.Executor, android.telephony.mbms.GroupCallCallback):
+
+SamShouldBeLast: android.telephony.MbmsStreamingSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsStreamingSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsStreamingSession#create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsStreamingSession#startStreaming(android.telephony.mbms.StreamingServiceInfo, java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback):
+
+SamShouldBeLast: android.telephony.SmsManager#getSmsMessagesForFinancialApp(android.os.Bundle, java.util.concurrent.Executor, android.telephony.SmsManager.FinancialSmsCallback):
+
+SamShouldBeLast: android.telephony.SubscriptionManager#addOnOpportunisticSubscriptionsChangedListener(java.util.concurrent.Executor, android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener):
+
+SamShouldBeLast: android.telephony.TelephonyManager#requestCellInfoUpdate(java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback):
+
+SamShouldBeLast: android.telephony.TelephonyManager#requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback):
+
+SamShouldBeLast: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+SamShouldBeLast: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
+
+SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
+
+SamShouldBeLast: android.view.View#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
+
+SamShouldBeLast: android.view.Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener, android.os.Handler):
+
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler):
+
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler):
+
+SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
+
+SamShouldBeLast: android.webkit.WebView#setWebViewRenderProcessClient(java.util.concurrent.Executor, android.webkit.WebViewRenderProcessClient):
+
+
+
+ServiceName: android.Manifest.permission#BIND_ATTENTION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_AUGMENTED_AUTOFILL_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_CELL_BROADCAST_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_CONTENT_SUGGESTIONS_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_EUICC_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_EXTERNAL_STORAGE_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_IMS_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_NETWORK_RECOMMENDATION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_NOTIFICATION_ASSISTANT_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_PRINT_RECOMMENDATION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_SETTINGS_SUGGESTIONS_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_SOUND_TRIGGER_DETECTION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TELEPHONY_DATA_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TELEPHONY_NETWORK_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TV_REMOTE_SERVICE:
+
+ServiceName: android.Manifest.permission#PROVIDE_RESOLVER_RANKER_SERVICE:
+
+ServiceName: android.Manifest.permission#REQUEST_NOTIFICATION_ASSISTANT_SERVICE:
+
+ServiceName: android.provider.DeviceConfig#NAMESPACE_PACKAGE_MANAGER_SERVICE:
+
diff --git a/api/test-current.txt b/api/test-current.txt
index 700be90..2c7e44d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -659,7 +659,7 @@
}
public abstract class Context {
- method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle);
+ method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.view.Display getDisplay();
method public abstract int getDisplayId();
@@ -731,6 +731,7 @@
method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
+ method @Nullable public String[] getTelephonyPackageNames();
method @Nullable public String getWellbeingPackageName();
method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
@@ -769,8 +770,10 @@
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+ field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
+ field public static final int PROTECTION_FLAG_WIFI = 8388608; // 0x800000
field @Nullable public final String backgroundPermission;
}
@@ -788,7 +791,9 @@
public final class AssetManager implements java.lang.AutoCloseable {
method @NonNull public String[] getApkPaths();
+ method @Nullable public String getLastResourceResolution();
method @Nullable public String getOverlayablesToString(String);
+ method public void setResourceResolutionLoggingEnabled(boolean);
}
public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable {
@@ -1089,11 +1094,14 @@
public class Location implements android.os.Parcelable {
method public void makeComplete();
+ method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
+ field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
method @NonNull public String[] getBackgroundThrottlingWhitelist();
method @NonNull public String[] getIgnoreSettingsWhitelist();
+ method @Nullable @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public java.util.List<java.lang.String> getProviderPackages(@NonNull String);
method @NonNull public java.util.List<android.location.LocationRequest> getTestProviderCurrentRequests(String);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
@@ -1741,6 +1749,7 @@
}
public class DeviceIdleManager {
+ method @RequiresPermission("android.permission.DEVICE_POWER") public int addPowerSaveWhitelistApps(@NonNull java.util.List<java.lang.String>);
method @NonNull public String[] getSystemPowerWhitelist();
method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
}
@@ -2244,8 +2253,11 @@
package android.permission {
public final class PermissionControllerManager {
+ method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
+ method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+ method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index cb27325..8af925a 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -230,6 +230,7 @@
"tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
"tests/e2e/Attribution_e2e_test.cpp",
"tests/e2e/ConfigTtl_e2e_test.cpp",
+ "tests/e2e/CountMetric_e2e_test.cpp",
"tests/e2e/DurationMetric_e2e_test.cpp",
"tests/e2e/GaugeMetric_e2e_pull_test.cpp",
"tests/e2e/GaugeMetric_e2e_push_test.cpp",
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index ff7416c..6c3dff2 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -16,24 +16,26 @@
#define DEBUG false // STOPSHIP if true
#include "Log.h"
-#include "statslog.h"
+
+#include "StatsLogProcessor.h"
#include <android-base/file.h>
#include <dirent.h>
#include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
-#include "StatsLogProcessor.h"
+#include <log/log_event_list.h>
+#include <utils/Errors.h>
+#include <utils/SystemClock.h>
+
#include "android-base/stringprintf.h"
#include "external/StatsPullerManager.h"
#include "guardrail/StatsdStats.h"
#include "metrics/CountMetricProducer.h"
+#include "state/StateManager.h"
#include "stats_log_util.h"
#include "stats_util.h"
+#include "statslog.h"
#include "storage/StorageManager.h"
-#include <log/log_event_list.h>
-#include <utils/Errors.h>
-#include <utils/SystemClock.h>
-
using namespace android;
using android::base::StringPrintf;
using android::util::FIELD_COUNT_REPEATED;
@@ -218,6 +220,8 @@
onIsolatedUidChangedEventLocked(*event);
}
+ StateManager::getInstance().onLogEvent(*event);
+
if (mMetricsManagers.empty()) {
return;
}
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 3d002d2..8292a3a 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -262,6 +262,10 @@
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
+ FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/anomaly/subscriber_util.cpp b/cmds/statsd/src/anomaly/subscriber_util.cpp
index e09d575..4c30c4c 100644
--- a/cmds/statsd/src/anomaly/subscriber_util.cpp
+++ b/cmds/statsd/src/anomaly/subscriber_util.cpp
@@ -40,7 +40,7 @@
for (const Subscription& subscription : subscriptions) {
if (subscription.probability_of_informing() < 1
- && ((float)rand() / RAND_MAX) >= subscription.probability_of_informing()) {
+ && ((float)rand() / (float)RAND_MAX) >= subscription.probability_of_informing()) {
// Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
// The config writer was advised to use -0.1 and 1.1 for never/always.
ALOGI("Fate decided that a subscriber would not be informed.");
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 43e33f5..5a76d1f 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -122,9 +122,10 @@
{.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
// system_elapsed_realtime
{android::util::SYSTEM_ELAPSED_REALTIME,
- {.pullTimeoutNs = NS_PER_SEC / 2,
- .coolDownNs = NS_PER_SEC,
- .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}},
+ {.coolDownNs = NS_PER_SEC,
+ .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
+ .pullTimeoutNs = NS_PER_SEC / 2,
+ }},
// system_uptime
{android::util::SYSTEM_UPTIME,
{.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index b5c8e35..4a06387 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -18,13 +18,15 @@
#include "Log.h"
#include "CountMetricProducer.h"
-#include "guardrail/StatsdStats.h"
-#include "stats_util.h"
-#include "stats_log_util.h"
+#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "stats_util.h"
+
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
@@ -65,16 +67,16 @@
const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
-CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric,
- const int conditionIndex,
- const sp<ConditionWizard>& wizard,
- const int64_t timeBaseNs, const int64_t startTimeNs,
- const unordered_map<int, shared_ptr<Activation>>&
- eventActivationMap,
- const unordered_map<int, vector<shared_ptr<Activation>>>&
- eventDeactivationMap)
+CountMetricProducer::CountMetricProducer(
+ const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t timeBaseNs, const int64_t startTimeNs,
+
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
: MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
- eventDeactivationMap) {
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
if (metric.has_bucket()) {
mBucketSizeNs =
TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
@@ -100,6 +102,8 @@
mConditionSliced = true;
}
+ // TODO(tsaichristine): b/142124705 handle metric state links
+
flushIfNeededLocked(startTimeNs);
// Adjust start for partial bucket
mCurrentBucketStartTimeNs = startTimeNs;
@@ -112,6 +116,12 @@
VLOG("~CountMetricProducer() called");
}
+void CountMetricProducer::onStateChanged(int atomId, const HashableDimensionKey& primaryKey,
+ int oldState, int newState) {
+ VLOG("CountMetric %lld onStateChanged State%d, key %s, %d -> %d", (long long)mMetricId, atomId,
+ primaryKey.toString().c_str(), oldState, newState);
+}
+
void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
if (mCurrentSlicedCounter == nullptr ||
mCurrentSlicedCounter->size() == 0) {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 61913c7..61e0892 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -17,15 +17,16 @@
#ifndef COUNT_METRIC_PRODUCER_H
#define COUNT_METRIC_PRODUCER_H
-#include <unordered_map>
-
#include <android/util/ProtoOutputStream.h>
#include <gtest/gtest_prod.h>
-#include "../anomaly/AnomalyTracker.h"
-#include "../condition/ConditionTracker.h"
-#include "../matchers/matcher_util.h"
+
+#include <unordered_map>
+
#include "MetricProducer.h"
+#include "anomaly/AnomalyTracker.h"
+#include "condition/ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "matchers/matcher_util.h"
#include "stats_util.h"
namespace android {
@@ -40,16 +41,20 @@
class CountMetricProducer : public MetricProducer {
public:
- CountMetricProducer(const ConfigKey& key, const CountMetric& countMetric,
- const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int64_t timeBaseNs, const int64_t startTimeNs,
- const std::unordered_map<int, std::shared_ptr<Activation>>&
- eventActivationMap = {},
- const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
- eventDeactivationMap = {});
+ CountMetricProducer(
+ const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t timeBaseNs, const int64_t startTimeNs,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~CountMetricProducer();
+ void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+ int newState) override;
+
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -106,6 +111,7 @@
FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
FRIEND_TEST(CountMetricProducerTest, TestFirstBucket);
+ FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 31b90f3..ab2a1c3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -69,9 +69,11 @@
const bool nesting, const sp<ConditionWizard>& wizard,
const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs,
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
- const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
: MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
- eventDeactivationMap),
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap),
mAggregationType(metric.aggregation_type()),
mStartIndex(startIndex),
mStopIndex(stopIndex),
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 0592b18..7457d7f 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -38,16 +38,16 @@
class DurationMetricProducer : public MetricProducer {
public:
- DurationMetricProducer(const ConfigKey& key, const DurationMetric& durationMetric,
- const int conditionIndex, const size_t startIndex,
- const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
- const sp<ConditionWizard>& wizard,
- const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
- const int64_t startTimeNs,
- const unordered_map<int, shared_ptr<Activation>>&
- eventActivationMap = {},
- const unordered_map<int, vector<shared_ptr<Activation>>>&
- eventDeactivationMap = {});
+ DurationMetricProducer(
+ const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
+ const size_t startIndex, const size_t stopIndex, const size_t stopAllIndex,
+ const bool nesting, const sp<ConditionWizard>& wizard,
+ const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
+ const int64_t startTimeNs,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {},
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~DurationMetricProducer();
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index a60a916..32eb077 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -52,16 +52,15 @@
const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1;
const int FIELD_ID_ATOMS = 2;
-EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric& metric,
- const int conditionIndex,
- const sp<ConditionWizard>& wizard,
- const int64_t startTimeNs,
- const unordered_map<int, shared_ptr<Activation>>&
- eventActivationMap,
- const unordered_map<int, vector<shared_ptr<Activation>>>&
- eventDeactivationMap)
+EventMetricProducer::EventMetricProducer(
+ const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t startTimeNs,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
: MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard, eventActivationMap,
- eventDeactivationMap) {
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
if (metric.links().size() > 0) {
for (const auto& link : metric.links()) {
Metric2Condition mc;
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index aab53c8..dca37e8 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -33,13 +33,14 @@
class EventMetricProducer : public MetricProducer {
public:
- EventMetricProducer(const ConfigKey& key, const EventMetric& eventMetric,
- const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int64_t startTimeNs,
- const std::unordered_map<int, std::shared_ptr<Activation>>&
- eventActivationMap = {},
- const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
- eventDeactivationMap = {});
+ EventMetricProducer(
+ const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t startTimeNs,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~EventMetricProducer();
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index e409b6fb..d0f88a8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -74,9 +74,11 @@
const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs,
const sp<StatsPullerManager>& pullerManager,
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
- const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
: MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
- eventDeactivationMap),
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap),
mWhatMatcherIndex(whatMatcherIndex),
mEventMatcherWizard(matcherWizard),
mPullerManager(pullerManager),
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index dfe1d56..640a02a 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -56,16 +56,17 @@
// producer always reports the guage at the earliest time of the bucket when the condition is met.
class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
public:
- GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
- const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
- const int whatMatcherIndex,const sp<EventMatcherWizard>& matcherWizard,
- const int pullTagId, const int triggerAtomId, const int atomId,
- const int64_t timeBaseNs, const int64_t startTimeNs,
- const sp<StatsPullerManager>& pullerManager,
- const std::unordered_map<int, std::shared_ptr<Activation>>&
- eventActivationMap = {},
- const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
- eventDeactivationMap = {});
+ GaugeMetricProducer(
+ const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
+ const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+ const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+ const int triggerAtomId, const int atomId, const int64_t timeBaseNs,
+ const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~GaugeMetricProducer();
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 3426a19..2a700ef 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -45,23 +45,27 @@
const int conditionIndex, const sp<ConditionWizard>& wizard,
const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
- eventDeactivationMap)
- : mMetricId(metricId),
- mConfigKey(key),
- mTimeBaseNs(timeBaseNs),
- mCurrentBucketStartTimeNs(timeBaseNs),
- mCurrentBucketNum(0),
- mCondition(initialCondition(conditionIndex)),
- mConditionTrackerIndex(conditionIndex),
- mConditionSliced(false),
- mWizard(wizard),
- mContainANYPositionInDimensionsInWhat(false),
- mSliceByPositionALL(false),
- mHasLinksToAllConditionDimensionsInTracker(false),
- mEventActivationMap(eventActivationMap),
- mEventDeactivationMap(eventDeactivationMap),
- mIsActive(mEventActivationMap.empty()) {
- }
+ eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : mMetricId(metricId),
+ mConfigKey(key),
+ mTimeBaseNs(timeBaseNs),
+ mCurrentBucketStartTimeNs(timeBaseNs),
+ mCurrentBucketNum(0),
+ mCondition(initialCondition(conditionIndex)),
+ mConditionTrackerIndex(conditionIndex),
+ mConditionSliced(false),
+ mWizard(wizard),
+ mContainANYPositionInDimensionsInWhat(false),
+ mSliceByPositionALL(false),
+ mHasLinksToAllConditionDimensionsInTracker(false),
+ mEventActivationMap(eventActivationMap),
+ mEventDeactivationMap(eventDeactivationMap),
+ mIsActive(mEventActivationMap.empty()),
+ mSlicedStateAtoms(slicedStateAtoms),
+ mStateGroupMap(stateGroupMap) {
+}
void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
if (!mIsActive) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 1e1eb69..a72de22 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,19 +17,19 @@
#ifndef METRIC_PRODUCER_H
#define METRIC_PRODUCER_H
-#include <shared_mutex>
-
#include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
+#include <log/logprint.h>
+#include <utils/RefBase.h>
+
+#include <unordered_map>
+
#include "HashableDimensionKey.h"
#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionWizard.h"
#include "config/ConfigKey.h"
#include "matchers/matcher_util.h"
#include "packages/PackageInfoListener.h"
-
-#include <log/logprint.h>
-#include <utils/RefBase.h>
-#include <unordered_map>
+#include "state/StateListener.h"
namespace android {
namespace os {
@@ -86,13 +86,15 @@
// writing the report to dropbox. MetricProducers should respond to package changes as required in
// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
// be a no-op.
-class MetricProducer : public virtual PackageInfoListener {
+class MetricProducer : public virtual PackageInfoListener, public virtual StateListener {
public:
MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
const int conditionIndex, const sp<ConditionWizard>& wizard,
const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
- eventDeactivationMap);
+ eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap);
virtual ~MetricProducer(){};
@@ -151,6 +153,9 @@
return mConditionSliced;
};
+ void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+ int newState){};
+
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
// This method clears all the past buckets.
void onDumpReport(const int64_t dumpTimeNs,
@@ -230,6 +235,11 @@
return mBucketSizeNs;
}
+ inline const std::vector<int> getSlicedStateAtoms() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mSlicedStateAtoms;
+ }
+
/* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
const sp<AlarmMonitor>& anomalyAlarmMonitor) {
@@ -381,6 +391,16 @@
bool mIsActive;
+ // The slice_by_state atom ids defined in statsd_config.
+ std::vector<int> mSlicedStateAtoms;
+
+ // Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>).
+ std::unordered_map<int, std::unordered_map<int, int64_t>> mStateGroupMap;
+
+ FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 963205e..7bae4b9 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,8 +15,12 @@
*/
#define DEBUG false // STOPSHIP if true
#include "Log.h"
+
#include "MetricsManager.h"
-#include "statslog.h"
+
+#include <log/logprint.h>
+#include <private/android_filesystem_config.h>
+#include <utils/SystemClock.h>
#include "CountMetricProducer.h"
#include "condition/CombinationConditionTracker.h"
@@ -25,12 +29,10 @@
#include "matchers/CombinationLogMatchingTracker.h"
#include "matchers/SimpleLogMatchingTracker.h"
#include "metrics_manager_util.h"
-#include "stats_util.h"
+#include "state/StateManager.h"
#include "stats_log_util.h"
-
-#include <log/logprint.h>
-#include <private/android_filesystem_config.h>
-#include <utils/SystemClock.h>
+#include "stats_util.h"
+#include "statslog.h"
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_INT32;
@@ -149,6 +151,12 @@
}
MetricsManager::~MetricsManager() {
+ for (auto it : mAllMetricProducers) {
+ for (int atomId : it->getSlicedStateAtoms()) {
+ StateManager::getInstance().unregisterListener(atomId, it);
+ }
+ }
+
VLOG("~MetricsManager()");
}
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 3dad614..d184121 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -282,6 +282,10 @@
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
+ FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 7fe5a83..6fd0327 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -83,9 +83,11 @@
const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int64_t timeBaseNs,
const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
- const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
: MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, conditionWizard,
- eventActivationMap, eventDeactivationMap),
+ eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap),
mWhatMatcherIndex(whatMatcherIndex),
mEventMatcherWizard(matcherWizard),
mPullerManager(pullerManager),
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index d7cd397..206e602 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -52,15 +52,17 @@
// - an alarm set to the end of the bucket
class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
public:
- ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
- const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
- const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard,
- const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs,
- const sp<StatsPullerManager>& pullerManager,
- const std::unordered_map<int, std::shared_ptr<Activation>>&
- eventActivationMap = {},
- const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
- eventDeactivationMap = {});
+ ValueMetricProducer(
+ const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex,
+ const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+ const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+ const int64_t timeBaseNs, const int64_t startTimeNs,
+ const sp<StatsPullerManager>& pullerManager,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~ValueMetricProducer();
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 0fee71e..33e162e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -20,24 +20,24 @@
#include "metrics_manager_util.h"
#include "MetricProducer.h"
-#include "../condition/CombinationConditionTracker.h"
-#include "../condition/SimpleConditionTracker.h"
-#include "../condition/StateConditionTracker.h"
-#include "../external/StatsPullerManager.h"
-#include "../matchers/CombinationLogMatchingTracker.h"
-#include "../matchers/SimpleLogMatchingTracker.h"
-#include "../matchers/EventMatcherWizard.h"
-#include "../metrics/CountMetricProducer.h"
-#include "../metrics/DurationMetricProducer.h"
-#include "../metrics/EventMetricProducer.h"
-#include "../metrics/GaugeMetricProducer.h"
-#include "../metrics/ValueMetricProducer.h"
+#include <inttypes.h>
+#include "condition/CombinationConditionTracker.h"
+#include "condition/SimpleConditionTracker.h"
+#include "condition/StateConditionTracker.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/CombinationLogMatchingTracker.h"
+#include "matchers/EventMatcherWizard.h"
+#include "matchers/SimpleLogMatchingTracker.h"
+#include "metrics/CountMetricProducer.h"
+#include "metrics/DurationMetricProducer.h"
+#include "metrics/EventMetricProducer.h"
+#include "metrics/GaugeMetricProducer.h"
+#include "metrics/ValueMetricProducer.h"
+#include "state/StateManager.h"
#include "stats_util.h"
#include "statslog.h"
-#include <inttypes.h>
-
using std::set;
using std::string;
using std::unordered_map;
@@ -138,6 +138,41 @@
return true;
}
+// Initializes state data structures for a metric.
+// input:
+// [config]: the input config
+// [stateIds]: the slice_by_state ids for this metric
+// [stateAtomIdMap]: this map contains the mapping from all state ids to atom ids
+// [allStateGroupMaps]: this map contains the mapping from state ids and state
+// values to state group ids for all states
+// output:
+// [slicedStateAtoms]: a vector of atom ids of all the slice_by_states
+// [stateGroupMap]: this map should contain the mapping from states ids and state
+// values to state group ids for all states that this metric
+// is interested in
+bool handleMetricWithStates(
+ const StatsdConfig& config, const ::google::protobuf::RepeatedField<int64_t>& stateIds,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+ vector<int>& slicedStateAtoms,
+ unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) {
+ for (const auto& stateId : stateIds) {
+ auto it = stateAtomIdMap.find(stateId);
+ if (it == stateAtomIdMap.end()) {
+ ALOGW("cannot find State %" PRId64 " in the config", stateId);
+ return false;
+ }
+ int atomId = it->second;
+ slicedStateAtoms.push_back(atomId);
+
+ auto stateIt = allStateGroupMaps.find(stateId);
+ if (stateIt != allStateGroupMaps.end()) {
+ stateGroupMap[atomId] = stateIt->second;
+ }
+ }
+ return true;
+}
+
// Validates a metricActivation and populates state.
// EventActivationMap and EventDeactivationMap are supplied to a MetricProducer
// to provide the producer with state about its activators and deactivators.
@@ -342,12 +377,32 @@
return true;
}
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+ unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps) {
+ for (int i = 0; i < config.state_size(); i++) {
+ const State& state = config.state(i);
+ const int64_t stateId = state.id();
+ stateAtomIdMap[stateId] = state.atom_id();
+
+ const StateMap& stateMap = state.map();
+ for (auto group : stateMap.group()) {
+ for (auto value : group.value()) {
+ allStateGroupMaps[stateId][value] = group.group_id();
+ }
+ }
+ }
+
+ return true;
+}
+
bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
const int64_t currentTimeNs, UidMap& uidMap,
const sp<StatsPullerManager>& pullerManager,
const unordered_map<int64_t, int>& logTrackerMap,
const unordered_map<int64_t, int>& conditionTrackerMap,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
unordered_map<int, vector<int>>& conditionToMetricMap,
@@ -398,10 +453,9 @@
int conditionIndex = -1;
if (metric.has_condition()) {
- bool good = handleMetricWithConditions(
- metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
- allConditionTrackers, conditionIndex, conditionToMetricMap);
- if (!good) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
return false;
}
} else {
@@ -411,6 +465,18 @@
}
}
+ std::vector<int> slicedStateAtoms;
+ unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+ if (metric.slice_by_state_size() > 0) {
+ if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+ allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+ return false;
+ }
+ }
+
+ // TODO(tsaichristine): add check for unequal number of MetricStateLinks
+ // and slice_by_states
+
unordered_map<int, shared_ptr<Activation>> eventActivationMap;
unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
bool success = handleMetricActivation(config, metric.id(), metricIndex,
@@ -421,7 +487,7 @@
sp<MetricProducer> countProducer = new CountMetricProducer(
key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs,
- eventActivationMap, eventDeactivationMap);
+ eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap);
allMetricProducers.push_back(countProducer);
}
@@ -721,6 +787,13 @@
}
for (const auto& it : allMetricProducers) {
uidMap.addListener(it);
+
+ // Register metrics to StateTrackers
+ for (int atomId : it->getSlicedStateAtoms()) {
+ if (!StateManager::getInstance().registerListener(atomId, it)) {
+ return false;
+ }
+ }
}
return true;
}
@@ -846,6 +919,8 @@
unordered_map<int64_t, int> logTrackerMap;
unordered_map<int64_t, int> conditionTrackerMap;
unordered_map<int64_t, int> metricProducerMap;
+ unordered_map<int64_t, int> stateAtomIdMap;
+ unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
ALOGE("initLogMatchingTrackers failed");
@@ -858,9 +933,13 @@
ALOGE("initConditionTrackers failed");
return false;
}
-
+ if (!initStates(config, stateAtomIdMap, allStateGroupMaps)) {
+ ALOGE("initStates failed");
+ return false;
+ }
if (!initMetrics(key, config, timeBaseNs, currentTimeNs, uidMap, pullerManager, logTrackerMap,
- conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers,
+ conditionTrackerMap, allAtomMatchers, stateAtomIdMap, allStateGroupMaps,
+ allConditionTrackers, allMetricProducers,
conditionToMetricMap, trackerToMetricMap, metricProducerMap,
noReportMetricIds, activationAtomTrackerToMetricMap,
deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 3802948..95b2ab8 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -68,6 +68,17 @@
std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks);
+// Initialize State maps using State protos in the config. These maps will
+// eventually be passed to MetricProducers to initialize their state info.
+// input:
+// [config]: the input config
+// output:
+// [stateAtomIdMap]: this map should contain the mapping from state ids to atom ids
+// [allStateGroupMaps]: this map should contain the mapping from states ids and state
+// values to state group ids for all states
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+ unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps);
+
// Initialize MetricProducers.
// input:
// [key]: the config key that this config belongs to
@@ -75,6 +86,9 @@
// [timeBaseSec]: start time base for all metrics
// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
// [conditionTrackerMap]: condition name to index mapping
+// [stateAtomIdMap]: contains the mapping from state ids to atom ids
+// [allStateGroupMaps]: contains the mapping from atom ids and state values to
+// state group ids for all states
// output:
// [allMetricProducers]: contains the list of sp to the MetricProducers created.
// [conditionToMetricMap]: contains the mapping from condition tracker index to
@@ -87,6 +101,8 @@
const std::unordered_map<int64_t, int>& conditionTrackerMap,
const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
index a3059c5..95b2c76 100644
--- a/cmds/statsd/src/state/StateManager.cpp
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -35,26 +35,25 @@
}
}
-bool StateManager::registerListener(int stateAtomId, wp<StateListener> listener) {
+bool StateManager::registerListener(int atomId, wp<StateListener> listener) {
std::lock_guard<std::mutex> lock(mMutex);
// Check if state tracker already exists
- if (mStateTrackers.find(stateAtomId) == mStateTrackers.end()) {
+ if (mStateTrackers.find(atomId) == mStateTrackers.end()) {
// Create a new state tracker iff atom is a state atom
- auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(stateAtomId);
+ auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(atomId);
if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
- mStateTrackers[stateAtomId] = new StateTracker(stateAtomId, it->second);
+ mStateTrackers[atomId] = new StateTracker(atomId, it->second);
} else {
- ALOGE("StateManager cannot register listener, Atom %d is not a state atom",
- stateAtomId);
+ ALOGE("StateManager cannot register listener, Atom %d is not a state atom", atomId);
return false;
}
}
- mStateTrackers[stateAtomId]->registerListener(listener);
+ mStateTrackers[atomId]->registerListener(listener);
return true;
}
-void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listener) {
+void StateManager::unregisterListener(int atomId, wp<StateListener> listener) {
std::unique_lock<std::mutex> lock(mMutex);
// Hold the sp<> until the lock is released so that ~StateTracker() is
@@ -62,7 +61,7 @@
sp<StateTracker> toRemove;
// Unregister listener from correct StateTracker
- auto it = mStateTrackers.find(stateAtomId);
+ auto it = mStateTrackers.find(atomId);
if (it != mStateTrackers.end()) {
it->second->unregisterListener(listener);
@@ -73,15 +72,15 @@
}
} else {
ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
- stateAtomId);
+ atomId);
}
lock.unlock();
}
-int StateManager::getState(int stateAtomId, const HashableDimensionKey& key) {
+int StateManager::getStateValue(int atomId, const HashableDimensionKey& key) {
std::lock_guard<std::mutex> lock(mMutex);
- if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
- return mStateTrackers[stateAtomId]->getState(key);
+ if (mStateTrackers.find(atomId) != mStateTrackers.end()) {
+ return mStateTrackers[atomId]->getStateValue(key);
}
return StateTracker::kStateUnknown;
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
index ce60f14..89ee6c0 100644
--- a/cmds/statsd/src/state/StateManager.h
+++ b/cmds/statsd/src/state/StateManager.h
@@ -15,10 +15,11 @@
*/
#pragma once
-//#include <utils/Log.h>
+#include <gtest/gtest_prod.h>
+#include <inttypes.h>
#include <utils/RefBase.h>
-#include "HashableDimensionKey.h"
+#include "HashableDimensionKey.h"
#include "state/StateListener.h"
#include "state/StateTracker.h"
@@ -38,29 +39,29 @@
// Notifies the correct StateTracker of an event.
void onLogEvent(const LogEvent& event);
- // Returns true if stateAtomId is the id of a state atom and notifies the
- // correct StateTracker to register the listener. If the correct
- // StateTracker does not exist, a new StateTracker is created.
- bool registerListener(int stateAtomId, wp<StateListener> listener);
+ // Returns true if atomId is being tracked and is associated with a state
+ // atom. StateManager notifies the correct StateTracker to register listener.
+ // If the correct StateTracker does not exist, a new StateTracker is created.
+ bool registerListener(int atomId, wp<StateListener> listener);
// Notifies the correct StateTracker to unregister a listener
// and removes the tracker if it no longer has any listeners.
- void unregisterListener(int stateAtomId, wp<StateListener> listener);
+ void unregisterListener(int atomId, wp<StateListener> listener);
- // Queries the correct StateTracker for the state that is mapped to the given
- // query key.
+ // Queries the correct StateTracker for the original/un-mapped state value
+ // that is mapped to the given query key.
// If the StateTracker doesn't exist, returns StateTracker::kStateUnknown.
- int getState(int stateAtomId, const HashableDimensionKey& queryKey);
+ int getStateValue(int atomId, const HashableDimensionKey& queryKey);
inline int getStateTrackersCount() {
std::lock_guard<std::mutex> lock(mMutex);
return mStateTrackers.size();
}
- inline int getListenersCount(int stateAtomId) {
+ inline int getListenersCount(int atomId) {
std::lock_guard<std::mutex> lock(mMutex);
- if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
- return mStateTrackers[stateAtomId]->getListenersCount();
+ if (mStateTrackers.find(atomId) != mStateTrackers.end()) {
+ return mStateTrackers[atomId]->getListenersCount();
}
return -1;
}
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index 5a91950..323fc0e 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -30,13 +30,13 @@
: mAtomId(atomId),
mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
// create matcher for each primary field
- // TODO(tsaichristine): handle when primary field is first uid in chain
+ // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain
for (const auto& primary : stateAtomInfo.primaryFields) {
Matcher matcher = getSimpleMatcher(atomId, primary);
mPrimaryFields.push_back(matcher);
}
- // TODO(tsaichristine): set default state, reset state, and nesting
+ // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting
}
void StateTracker::onLogEvent(const LogEvent& event) {
@@ -96,7 +96,7 @@
mListeners.erase(listener);
}
-int StateTracker::getState(const HashableDimensionKey& queryKey) const {
+int StateTracker::getStateValue(const HashableDimensionKey& queryKey) const {
if (queryKey.getValues().size() == mPrimaryFields.size()) {
auto it = mStateMap.find(queryKey);
if (it != mStateMap.end()) {
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index f22706c..cfa9fd8 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -48,7 +48,7 @@
// Returns the state value mapped to the given query key.
// If the key isn't mapped to a state or the key size doesn't match the
// primary key size, the default state is returned.
- int getState(const HashableDimensionKey& queryKey) const;
+ int getStateValue(const HashableDimensionKey& queryKey) const;
inline int getListenersCount() const {
return mListeners.size();
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 67625eb..c22e3cc 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -445,6 +445,8 @@
return 12 * 60 * 60 * 1000LL;
case ONE_DAY:
return 24 * 60 * 60 * 1000LL;
+ case ONE_WEEK:
+ return 7 * 24 * 60 * 60 * 1000LL;
case CTS:
return 1000;
case TIME_UNIT_UNSPECIFIED:
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index c107397..c98b2cf 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -44,6 +44,7 @@
SIX_HOURS = 7;
TWELVE_HOURS = 8;
ONE_DAY = 9;
+ ONE_WEEK = 10;
CTS = 1000;
}
diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
new file mode 100644
index 0000000..6591d69
--- /dev/null
+++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/state/StateManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+TEST(CountMetricE2eTest, TestWithSimpleState) {
+ // Initialize config
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+
+ auto state = CreateScreenState();
+ *config.add_state() = state;
+
+ // Create count metric that slices by screen state
+ int64_t metricId = 123456;
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(metricId);
+ countMetric->set_what(syncStartMatcher.id());
+ countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+ countMetric->add_slice_by_state(state.id());
+
+ // Initialize StatsLogProcessor
+ const int64_t baseTimeNs = 0; // 0:00
+ const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+ const int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+
+ auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+ // Check that StateTrackers were properly initialized.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1,
+ StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+ // Check that CountMetricProducer was initialized correctly.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+}
+
+TEST(CountMetricE2eTest, TestWithMappedState) {
+ // Initialize config
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+
+ auto state = CreateScreenStateWithOnOffMap();
+ *config.add_state() = state;
+
+ // Create count metric that slices by screen state with on/off map
+ int64_t metricId = 123456;
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(metricId);
+ countMetric->set_what(syncStartMatcher.id());
+ countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+ countMetric->add_slice_by_state(state.id());
+
+ // Initialize StatsLogProcessor
+ const int64_t baseTimeNs = 0; // 0:00
+ const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+ const int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+
+ auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+ // Check that StateTrackers were properly initialized.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1,
+ StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+ // Check that CountMetricProducer was initialized correctly.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+ StateMap map = state.map();
+ for (auto group : map.group()) {
+ for (auto value : group.value()) {
+ EXPECT_EQ(metricProducer->mStateGroupMap[android::util::SCREEN_STATE_CHANGED][value],
+ group.group_id());
+ }
+ }
+}
+
+TEST(CountMetricE2eTest, TestWithMultipleStates) {
+ // Initialize config
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+
+ auto state1 = CreateScreenStateWithOnOffMap();
+ *config.add_state() = state1;
+ auto state2 = CreateUidProcessState();
+ *config.add_state() = state2;
+
+ // Create count metric that slices by screen state with on/off map
+ int64_t metricId = 123456;
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(metricId);
+ countMetric->set_what(syncStartMatcher.id());
+ countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+ countMetric->add_slice_by_state(state1.id());
+ countMetric->add_slice_by_state(state2.id());
+
+ // Initialize StatsLogProcessor
+ const int64_t baseTimeNs = 0; // 0:00
+ const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+ const int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+
+ auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+ // Check that StateTrackers were properly initialized.
+ EXPECT_EQ(2, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1,
+ StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+ android::util::UID_PROCESS_STATE_CHANGED));
+
+ // Check that CountMetricProducer was initialized correctly.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 2);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(1), android::util::UID_PROCESS_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+ StateMap map = state1.map();
+ for (auto group : map.group()) {
+ for (auto value : group.value()) {
+ EXPECT_EQ(metricProducer->mStateGroupMap[android::util::SCREEN_STATE_CHANGED][value],
+ group.group_id());
+ }
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 839daa4..8915c73 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -43,8 +43,8 @@
metric.set_bucket(ONE_MINUTE);
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- 5, 600 * NS_PER_SEC + NS_PER_SEC/2);
+ CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, 5,
+ 600 * NS_PER_SEC + NS_PER_SEC / 2);
EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs);
EXPECT_EQ(10, countProducer.mCurrentBucketNum);
EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs());
@@ -131,7 +131,8 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs, bucketStartTimeNs);
+ CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs,
+ bucketStartTimeNs);
countProducer.onConditionChanged(true, bucketStartTimeNs);
countProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
@@ -396,6 +397,26 @@
std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
}
+TEST(CountMetricProducerTest, TestOneWeekTimeUnit) {
+ CountMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_WEEK);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ int64_t oneDayNs = 24 * 60 * 60 * 1e9;
+ int64_t fiveWeeksNs = 5 * 7 * oneDayNs;
+
+ CountMetricProducer countProducer(
+ kConfigKey, metric, -1 /* meaning no condition */, wizard, oneDayNs, fiveWeeksNs);
+
+ int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs;
+
+ EXPECT_EQ(fiveWeeksNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(4, countProducer.mCurrentBucketNum);
+ EXPECT_EQ(fiveWeeksOneDayNs, countProducer.getCurrentBucketEndTimeNs());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index c89ffea..8d38000 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -44,7 +44,7 @@
std::vector<Update> updates;
- void onStateChanged(int stateAtomId, const HashableDimensionKey& primaryKey, int oldState,
+ void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
int newState) {
updates.emplace_back(primaryKey, newState);
}
@@ -82,6 +82,7 @@
return event;
}
+// Incorrect event - missing fields
std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, int state) {
std::shared_ptr<LogEvent> event =
std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
@@ -91,6 +92,18 @@
event->init();
return event;
}
+
+// Incorrect event - exclusive state has wrong type
+std::shared_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::string& packageName) {
+ std::shared_ptr<LogEvent> event =
+ std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+ event->write((int32_t)uid);
+ event->write(packageName);
+ event->write(true);
+ event->write("string"); // exclusive state: string instead of int
+ event->init();
+ return event;
+}
// END: build event functions.
// START: get primary key functions
@@ -148,22 +161,22 @@
// Register listener to non-existing StateTracker
EXPECT_EQ(0, mgr.getStateTrackersCount());
- mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+ EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1));
EXPECT_EQ(1, mgr.getStateTrackersCount());
EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
// Register listener to existing StateTracker
- mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+ EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2));
EXPECT_EQ(1, mgr.getStateTrackersCount());
EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
// Register already registered listener to existing StateTracker
- mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+ EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2));
EXPECT_EQ(1, mgr.getStateTrackersCount());
EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
// Register listener to non-state atom
- mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2);
+ EXPECT_FALSE(mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2));
EXPECT_EQ(1, mgr.getStateTrackersCount());
}
@@ -227,12 +240,12 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
- EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, queryKey));
+ EXPECT_EQ(2, mgr.getStateValue(android::util::SCREEN_STATE_CHANGED, queryKey));
}
/**
* Test StateManager's onLogEvent and StateListener's onStateChanged correctly
- * updates listener for states with primary keys.
+ * updates listener for states with one primary key.
*/
TEST(StateTrackerTest, TestStateChangeOnePrimaryField) {
sp<TestStateListener> listener1 = new TestStateListener();
@@ -240,9 +253,8 @@
mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener1);
// log event
- std::shared_ptr<LogEvent> event = buildUidProcessEvent(
- 1000,
- android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
+ std::shared_ptr<LogEvent> event =
+ buildUidProcessEvent(1000 /* uid */, android::app::ProcessStateEnum::PROCESS_STATE_TOP);
mgr.onLogEvent(*event);
// check listener was updated
@@ -252,23 +264,33 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey;
- getUidProcessKey(1000, &queryKey);
- EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+ getUidProcessKey(1000 /* uid */, &queryKey);
+ EXPECT_EQ(1002, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
}
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with multiple primary keys.
+ */
TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) {
sp<TestStateListener> listener1 = new TestStateListener();
StateManager mgr;
mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
// log event
- std::shared_ptr<LogEvent> event = buildOverlayEvent(1000, "package1", 1); // state: ENTERED
+ std::shared_ptr<LogEvent> event =
+ buildOverlayEvent(1000 /* uid */, "package1", 1); // state: ENTERED
mgr.onLogEvent(*event);
- // check listener update
+ // check listener was updated
EXPECT_EQ(1, listener1->updates.size());
EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
EXPECT_EQ(1, listener1->updates[0].mState);
+
+ // check StateTracker was updated by querying for state
+ HashableDimensionKey queryKey;
+ getOverlayKey(1000 /* uid */, "package1", &queryKey);
+ EXPECT_EQ(1, mgr.getStateValue(android::util::OVERLAY_STATE_CHANGED, queryKey));
}
/**
@@ -282,11 +304,14 @@
mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
// log event
- std::shared_ptr<LogEvent> event =
- buildIncorrectOverlayEvent(1000, "package1", 1); // state: ENTERED
- mgr.onLogEvent(*event);
+ std::shared_ptr<LogEvent> event1 =
+ buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */);
+ std::shared_ptr<LogEvent> event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2");
- // check listener update
+ // check listener was updated
+ mgr.onLogEvent(*event1);
+ EXPECT_EQ(0, listener1->updates.size());
+ mgr.onLogEvent(*event2);
EXPECT_EQ(0, listener1->updates.size());
}
@@ -301,18 +326,19 @@
std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
1000,
- android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
+ android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
std::shared_ptr<LogEvent> event2 = buildUidProcessEvent(
1001,
- android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE); // state value: 1003
+ android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE); // state value:
+ // 1003
std::shared_ptr<LogEvent> event3 = buildUidProcessEvent(
1002,
- android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT); // state value: 1000
+ android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT); // state value: 1000
std::shared_ptr<LogEvent> event4 = buildUidProcessEvent(
1001,
- android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
+ android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
std::shared_ptr<LogEvent> event5 =
- buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON); // state value:
+ buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
@@ -327,25 +353,25 @@
// Query for UidProcessState of uid 1001
HashableDimensionKey queryKey1;
getUidProcessKey(1001, &queryKey1);
- EXPECT_EQ(1003, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(1003, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for UidProcessState of uid 1004 - not in state map
HashableDimensionKey queryKey2;
getUidProcessKey(1004, &queryKey2);
- EXPECT_EQ(-1,
- mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey2)); // default state
+ EXPECT_EQ(-1, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED,
+ queryKey2)); // default state
// Query for UidProcessState of uid 1001 - after change in state
mgr.onLogEvent(*event4);
- EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(1002, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for ScreenState
- EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+ EXPECT_EQ(2, mgr.getStateValue(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
// Query for OverlayState of uid 1000, package name "package2"
HashableDimensionKey queryKey3;
getOverlayKey(1000, "package2", &queryKey3);
- EXPECT_EQ(2, mgr.getState(android::util::OVERLAY_STATE_CHANGED, queryKey3));
+ EXPECT_EQ(2, mgr.getStateValue(android::util::OVERLAY_STATE_CHANGED, queryKey3));
}
} // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 2c4f3c7..38c22ab 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -251,6 +251,101 @@
return predicate;
}
+State CreateScreenState() {
+ State state;
+ state.set_id(StringToId("ScreenState"));
+ state.set_atom_id(29);
+ return state;
+}
+
+State CreateUidProcessState() {
+ State state;
+ state.set_id(StringToId("UidProcessState"));
+ state.set_atom_id(27);
+ return state;
+}
+
+State CreateOverlayState() {
+ State state;
+ state.set_id(StringToId("OverlayState"));
+ state.set_atom_id(59);
+ return state;
+}
+
+State CreateScreenStateWithOnOffMap() {
+ State state;
+ state.set_id(StringToId("ScreenStateOnOff"));
+ state.set_atom_id(29);
+
+ auto map = CreateScreenStateOnOffMap();
+ *state.mutable_map() = map;
+
+ return state;
+}
+
+State CreateScreenStateWithInDozeMap() {
+ State state;
+ state.set_id(StringToId("ScreenStateInDoze"));
+ state.set_atom_id(29);
+
+ auto map = CreateScreenStateInDozeMap();
+ *state.mutable_map() = map;
+
+ return state;
+}
+
+StateMap_StateGroup CreateScreenStateOnGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_ON"));
+ group.add_value(2);
+ group.add_value(5);
+ group.add_value(6);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateOffGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_OFF"));
+ group.add_value(0);
+ group.add_value(1);
+ group.add_value(3);
+ group.add_value(4);
+ return group;
+}
+
+StateMap CreateScreenStateOnOffMap() {
+ StateMap map;
+ *map.add_group() = CreateScreenStateOnGroup();
+ *map.add_group() = CreateScreenStateOffGroup();
+ return map;
+}
+
+StateMap_StateGroup CreateScreenStateInDozeGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_DOZE"));
+ group.add_value(3);
+ group.add_value(4);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateNotDozeGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_NOT_DOZE"));
+ group.add_value(0);
+ group.add_value(1);
+ group.add_value(2);
+ group.add_value(5);
+ group.add_value(6);
+ return group;
+}
+
+StateMap CreateScreenStateInDozeMap() {
+ StateMap map;
+ *map.add_group() = CreateScreenStateInDozeGroup();
+ *map.add_group() = CreateScreenStateNotDozeGroup();
+ return map;
+}
+
void addPredicateToPredicateCombination(const Predicate& predicate,
Predicate* combinationPredicate) {
combinationPredicate->mutable_combination()->add_predicate(predicate.id());
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 635c583..c026105 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -104,6 +104,37 @@
// Create a Predicate proto for app is in background.
Predicate CreateIsInBackgroundPredicate();
+// Create State proto for screen state atom.
+State CreateScreenState();
+
+// Create State proto for uid process state atom.
+State CreateUidProcessState();
+
+// Create State proto for overlay state atom.
+State CreateOverlayState();
+
+State CreateScreenStateWithOnOffMap();
+
+State CreateScreenStateWithInDozeMap();
+
+// Create StateGroup proto for ScreenState ON group
+StateMap_StateGroup CreateScreenStateOnGroup();
+
+// Create StateGroup proto for ScreenState OFF group
+StateMap_StateGroup CreateScreenStateOffGroup();
+
+// Create StateMap proto for ScreenState ON/OFF map
+StateMap CreateScreenStateOnOffMap();
+
+// Create StateGroup proto for ScreenState IN DOZE group
+StateMap_StateGroup CreateScreenStateInDozeGroup();
+
+// Create StateGroup proto for ScreenState NOT IN DOZE group
+StateMap_StateGroup CreateScreenStateNotDozeGroup();
+
+// Create StateMap proto for ScreenState IN DOZE map
+StateMap CreateScreenStateInDozeMap();
+
// Add a predicate to the predicate combination.
void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
@@ -319,4 +350,4 @@
}
} // namespace statsd
} // namespace os
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 86bf20a..03ef286 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3160,6 +3160,15 @@
}
@Override
+ public String[] getTelephonyPackageNames() {
+ try {
+ return mPM.getTelephonyPackageNames();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
public String getSystemCaptionsServicePackageName() {
try {
return mPM.getSystemCaptionsServicePackageName();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 39fab63..d5bc9b0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2212,9 +2212,9 @@
}
@Override
- public Context createContextAsUser(UserHandle user) {
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
try {
- return createPackageContextAsUser(getPackageName(), mFlags, user);
+ return createPackageContextAsUser(getPackageName(), flags, user);
} catch (NameNotFoundException e) {
throw new IllegalStateException("Own package not found: package=" + getPackageName());
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index dda3bb5..f591441 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -215,7 +215,6 @@
void releaseSomeActivities(in IApplicationThread app);
Bitmap getTaskDescriptionIcon(in String filename, int userId);
- void startInPlaceAnimationOnFrontMostApplication(in Bundle opts);
void registerTaskStackListener(in ITaskStackListener listener);
void unregisterTaskStackListener(in ITaskStackListener listener);
void setTaskResizeable(int taskId, int resizeableMode);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 316cab8..58f6741 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -997,8 +997,13 @@
}
/**
- * @hide
+ * <p>
+ * Gets the currently applied notification policy. If {@link #getCurrentInterruptionFilter}
+ * is equal to {@link #INTERRUPTION_FILTER_ALL}, then the consolidated notification policy
+ * will match the default notification policy returned by {@link #getNotificationPolicy}.
+ * </p>
*/
+ @Nullable
public NotificationManager.Policy getConsolidatedNotificationPolicy() {
INotificationManager service = getService();
try {
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index cb9ebac..68ab89c 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -32,6 +32,7 @@
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.ResourcesKey;
+import android.content.res.loader.ResourceLoader;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.os.Process;
@@ -45,6 +46,7 @@
import android.view.Display;
import android.view.DisplayAdjustments;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -53,7 +55,11 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
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.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -92,6 +98,52 @@
new ArrayMap<>();
/**
+ * A list of {@link Resources} that contain unique {@link ResourcesImpl}s.
+ *
+ * These are isolated so that {@link ResourceLoader}s can be added and removed without
+ * affecting other instances.
+ *
+ * When a reference is added here, it is guaranteed that the {@link ResourcesImpl}
+ * it contains is unique to itself and will never be set to a shared reference.
+ */
+ @GuardedBy("this")
+ private List<ResourcesWithLoaders> mResourcesWithLoaders = Collections.emptyList();
+
+ private static class ResourcesWithLoaders {
+
+ private WeakReference<Resources> mResources;
+ private ResourcesKey mResourcesKey;
+
+ @Nullable
+ private WeakReference<IBinder> mActivityToken;
+
+ ResourcesWithLoaders(Resources resources, ResourcesKey resourcesKey,
+ IBinder activityToken) {
+ this.mResources = new WeakReference<>(resources);
+ this.mResourcesKey = resourcesKey;
+ this.mActivityToken = new WeakReference<>(activityToken);
+ }
+
+ @Nullable
+ Resources resources() {
+ return mResources.get();
+ }
+
+ @Nullable
+ IBinder activityToken() {
+ return mActivityToken == null ? null : mActivityToken.get();
+ }
+
+ ResourcesKey resourcesKey() {
+ return mResourcesKey;
+ }
+
+ void updateKey(ResourcesKey newKey) {
+ mResourcesKey = newKey;
+ }
+ }
+
+ /**
* A list of Resource references that can be reused.
*/
@UnsupportedAppUsage
@@ -182,15 +234,36 @@
public void invalidatePath(String path) {
synchronized (this) {
int count = 0;
- for (int i = 0; i < mResourceImpls.size();) {
+
+ for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
final ResourcesKey key = mResourceImpls.keyAt(i);
if (key.isPathReferenced(path)) {
- cleanupResourceImpl(key);
+ ResourcesImpl impl = mResourceImpls.removeAt(i).get();
+ if (impl != null) {
+ impl.flushLayoutCache();
+ }
count++;
- } else {
- i++;
}
}
+
+ for (int i = mResourcesWithLoaders.size() - 1; i >= 0; i--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(i);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ final ResourcesKey key = resourcesWithLoaders.resourcesKey();
+ if (key.isPathReferenced(path)) {
+ mResourcesWithLoaders.remove(i);
+ ResourcesImpl impl = resources.getImpl();
+ if (impl != null) {
+ impl.flushLayoutCache();
+ }
+ count++;
+ }
+ }
+
Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
@@ -317,15 +390,6 @@
}
}
- private void cleanupResourceImpl(ResourcesKey removedKey) {
- // Remove resource key to resource impl mapping and flush cache
- final ResourcesImpl res = mResourceImpls.remove(removedKey).get();
-
- if (res != null) {
- res.flushLayoutCache();
- }
- }
-
private static String overlayPathToIdmapPath(String path) {
return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
}
@@ -499,6 +563,16 @@
pw.print("resource impls: ");
pw.println(countLiveReferences(mResourceImpls.values()));
+
+ int resourcesWithLoadersCount = 0;
+ for (int index = 0; index < mResourcesWithLoaders.size(); index++) {
+ if (mResourcesWithLoaders.get(index).resources() != null) {
+ resourcesWithLoadersCount++;
+ }
+ }
+
+ pw.print("resources with loaders: ");
+ pw.println(resourcesWithLoadersCount);
}
}
@@ -579,11 +653,24 @@
*/
private @Nullable ResourcesKey findKeyForResourceImplLocked(
@NonNull ResourcesImpl resourceImpl) {
- final int refCount = mResourceImpls.size();
+ int size = mResourcesWithLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ if (resourceImpl == resources.getImpl()) {
+ return resourcesWithLoaders.resourcesKey();
+ }
+ }
+
+ int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
- if (impl != null && resourceImpl == impl) {
+ if (resourceImpl == impl) {
return mResourceImpls.keyAt(i);
}
}
@@ -625,31 +712,55 @@
return activityResources;
}
- /**
- * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
- * or the class loader is different.
- */
- private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
+ @Nullable
+ private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
+ @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
+ int size = mResourcesWithLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ IBinder activityToken = resourcesWithLoaders.activityToken();
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+
+ ClassLoader classLoader = resources.getClassLoader();
+
+ if (Objects.equals(activityToken, targetActivityToken)
+ && Objects.equals(key, targetKey)
+ && Objects.equals(classLoader, targetClassLoader)) {
+ return resources;
+ }
+ }
+
+ ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ targetActivityToken);
+
+ size = activityResources.activityResources.size();
+ for (int index = 0; index < size; index++) {
+ WeakReference<Resources> ref = activityResources.activityResources.get(index);
+ Resources resources = ref.get();
+ ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
+ resources.getImpl());
+
+ if (key != null
+ && Objects.equals(resources.getClassLoader(), targetClassLoader)
+ && Objects.equals(key, targetKey)) {
+ return resources;
+ }
+ }
+
+ return null;
+ }
+
+ private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
@NonNull CompatibilityInfo compatInfo) {
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
activityToken);
- final int refCount = activityResources.activityResources.size();
- for (int i = 0; i < refCount; i++) {
- WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
- Resources resources = weakResourceRef.get();
-
- if (resources != null
- && Objects.equals(resources.getClassLoader(), classLoader)
- && resources.getImpl() == impl) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
- }
- return resources;
- }
- }
-
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
@@ -661,28 +772,8 @@
return resources;
}
- /**
- * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
- * otherwise creates a new Resources object.
- */
- private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
+ private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
- // Find an existing Resources that has this ResourcesImpl set.
- final int refCount = mResourceReferences.size();
- for (int i = 0; i < refCount; i++) {
- WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
- Resources resources = weakResourceRef.get();
- if (resources != null &&
- Objects.equals(resources.getClassLoader(), classLoader) &&
- resources.getImpl() == impl) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
- }
- return resources;
- }
- }
-
- // Create a new Resources reference and use the existing ResourcesImpl object.
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
@@ -750,16 +841,73 @@
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
+ cleanupReferences(activityToken);
+ rebaseKeyForActivity(activityToken, key);
+
+ synchronized (this) {
+ Resources resources = findResourcesForActivityLocked(activityToken, key,
+ classLoader);
+ if (resources != null) {
+ return resources;
+ }
+ }
+
// Now request an actual Resources object.
- return getOrCreateResources(activityToken, key, classLoader);
+ return createResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
/**
- * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
- * or creates one if it doesn't exist.
+ * Rebases a key's override config on top of the Activity's base override.
+ */
+ private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) {
+ final ActivityResources activityResources =
+ getOrCreateActivityResourcesStructLocked(activityToken);
+
+ // Clean up any dead references so they don't pile up.
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+
+ // Rebase the key's override config on top of the Activity's base override.
+ if (key.hasOverrideConfiguration()
+ && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
+ final Configuration temp = new Configuration(activityResources.overrideConfig);
+ temp.updateFrom(key.mOverrideConfiguration);
+ key.mOverrideConfiguration.setTo(temp);
+ }
+ }
+
+ /**
+ * Check WeakReferences and remove any dead references so they don't pile up.
+ * @param activityToken optional token to clean up Activity resources
+ */
+ private void cleanupReferences(IBinder activityToken) {
+ synchronized (this) {
+ if (activityToken != null) {
+ ActivityResources activityResources = mActivityResourceReferences.get(
+ activityToken);
+ if (activityResources != null) {
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+ }
+ } else {
+ ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
+ }
+
+ for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ mResourcesWithLoaders.remove(index);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a Resources object set with a ResourcesImpl object matching the given key.
*
* @param activityToken The Activity this Resources object should be associated with.
* @param key The key describing the parameters of the ResourcesImpl object.
@@ -769,7 +917,7 @@
* {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
* is called.
*/
- private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
+ private @Nullable Resources createResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (DEBUG) {
@@ -778,66 +926,17 @@
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
- if (activityToken != null) {
- final ActivityResources activityResources =
- getOrCreateActivityResourcesStructLocked(activityToken);
-
- // Clean up any dead references so they don't pile up.
- ArrayUtils.unstableRemoveIf(activityResources.activityResources,
- sEmptyReferencePredicate);
-
- // Rebase the key's override config on top of the Activity's base override.
- if (key.hasOverrideConfiguration()
- && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
- final Configuration temp = new Configuration(activityResources.overrideConfig);
- temp.updateFrom(key.mOverrideConfiguration);
- key.mOverrideConfiguration.setTo(temp);
- }
-
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
- resourcesImpl, key.mCompatInfo);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
-
- } else {
- // Clean up any dead references so they don't pile up.
- ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
-
- // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
- }
-
- // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
- ResourcesImpl resourcesImpl = createResourcesImpl(key);
+ ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
if (resourcesImpl == null) {
return null;
}
- // Add this ResourcesImpl to the cache.
- mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
-
- final Resources resources;
if (activityToken != null) {
- resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+ return createResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
- resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
+ return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
- return resources;
}
}
@@ -868,7 +967,8 @@
* {@link ClassLoader#getSystemClassLoader()} is used.
* @return a Resources object from which to access resources.
*/
- public @Nullable Resources getResources(@Nullable IBinder activityToken,
+ public @Nullable Resources getResources(
+ @Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@@ -888,7 +988,14 @@
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
- return getOrCreateResources(activityToken, key, classLoader);
+
+ cleanupReferences(activityToken);
+
+ if (activityToken != null) {
+ rebaseKeyForActivity(activityToken, key);
+ }
+
+ return createResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
@@ -944,67 +1051,40 @@
here);
}
- final boolean activityHasOverrideConfig =
- !activityResources.overrideConfig.equals(Configuration.EMPTY);
// Rebase each Resources associated with this Activity.
final int refCount = activityResources.activityResources.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResRef = activityResources.activityResources.get(
i);
+
Resources resources = weakResRef.get();
if (resources == null) {
continue;
}
- // Extract the ResourcesKey that was last used to create the Resources for this
- // activity.
- final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
- if (oldKey == null) {
- Slog.e(TAG, "can't find ResourcesKey for resources impl="
- + resources.getImpl());
+ ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+ overrideConfig, displayId);
+ updateActivityResources(resources, newKey, false);
+ }
+
+ // Also find loaders that are associated with an Activity
+ final int loaderCount = mResourcesWithLoaders.size();
+ for (int index = loaderCount - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(
+ index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null
+ || resourcesWithLoaders.activityToken() != activityToken) {
continue;
}
- // Build the new override configuration for this ResourcesKey.
- final Configuration rebasedOverrideConfig = new Configuration();
- if (overrideConfig != null) {
- rebasedOverrideConfig.setTo(overrideConfig);
- }
+ ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+ overrideConfig, displayId);
- if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
- // Generate a delta between the old base Activity override configuration and
- // the actual final override configuration that was used to figure out the
- // real delta this Resources object wanted.
- Configuration overrideOverrideConfig = Configuration.generateDelta(
- oldConfig, oldKey.mOverrideConfiguration);
- rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
- }
+ updateActivityResources(resources, newKey, true);
- // Create the new ResourcesKey with the rebased override config.
- final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
- oldKey.mSplitResDirs,
- oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
- rebasedOverrideConfig, oldKey.mCompatInfo);
-
- if (DEBUG) {
- Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
- + " to newKey=" + newKey + ", displayId=" + displayId);
- }
-
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
- if (resourcesImpl == null) {
- resourcesImpl = createResourcesImpl(newKey);
- if (resourcesImpl != null) {
- mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
- }
- }
-
- if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
- // Set the ResourcesImpl, updating it for all users of this Resources
- // object.
- resources.setImpl(resourcesImpl);
- }
+ resourcesWithLoaders.updateKey(newKey);
}
}
} finally {
@@ -1012,6 +1092,70 @@
}
}
+ /**
+ * Rebases an updated override config over any old override config and returns the new one
+ * that an Activity's Resources should be set to.
+ */
+ private ResourcesKey rebaseActivityOverrideConfig(Resources resources,
+ Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig,
+ int displayId) {
+ // Extract the ResourcesKey that was last used to create the Resources for this
+ // activity.
+ final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+ if (oldKey == null) {
+ Slog.e(TAG, "can't find ResourcesKey for resources impl="
+ + resources.getImpl());
+ return null;
+ }
+
+ // Build the new override configuration for this ResourcesKey.
+ final Configuration rebasedOverrideConfig = new Configuration();
+ if (newOverrideConfig != null) {
+ rebasedOverrideConfig.setTo(newOverrideConfig);
+ }
+
+ final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY);
+ if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) {
+ // Generate a delta between the old base Activity override configuration and
+ // the actual final override configuration that was used to figure out the
+ // real delta this Resources object wanted.
+ Configuration overrideOverrideConfig = Configuration.generateDelta(
+ oldOverrideConfig, oldKey.mOverrideConfiguration);
+ rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+ }
+
+ // Create the new ResourcesKey with the rebased override config.
+ final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
+ oldKey.mSplitResDirs,
+ oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
+ rebasedOverrideConfig, oldKey.mCompatInfo);
+
+ if (DEBUG) {
+ Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
+ + " to newKey=" + newKey + ", displayId=" + displayId);
+ }
+
+ return newKey;
+ }
+
+ private void updateActivityResources(Resources resources, ResourcesKey newKey,
+ boolean hasLoader) {
+ final ResourcesImpl resourcesImpl;
+
+ if (hasLoader) {
+ // Loaders always get new Impls because they cannot be shared
+ resourcesImpl = createResourcesImpl(newKey);
+ } else {
+ resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
+ }
+
+ if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
+ // Set the ResourcesImpl, updating it for all users of this Resources
+ // object.
+ resources.setImpl(resourcesImpl);
+ }
+ }
+
@TestApi
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
@@ -1050,61 +1194,77 @@
ApplicationPackageManager.configurationChanged();
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
- Configuration tmpConfig = null;
+ Configuration tmpConfig = new Configuration();
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
ResourcesKey key = mResourceImpls.keyAt(i);
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
if (r != null) {
- if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
- + r + " config to: " + config);
- int displayId = key.mDisplayId;
- boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
- DisplayMetrics dm = defaultDisplayMetrics;
- final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
- if (!isDefaultDisplay || hasOverrideConfiguration) {
- if (tmpConfig == null) {
- tmpConfig = new Configuration();
- }
- tmpConfig.setTo(config);
-
- // Get new DisplayMetrics based on the DisplayAdjustments given
- // to the ResourcesImpl. Update a copy if the CompatibilityInfo
- // changed, because the ResourcesImpl object will handle the
- // update internally.
- DisplayAdjustments daj = r.getDisplayAdjustments();
- if (compat != null) {
- daj = new DisplayAdjustments(daj);
- daj.setCompatibilityInfo(compat);
- }
- dm = getDisplayMetrics(displayId, daj);
-
- if (!isDefaultDisplay) {
- applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
- }
-
- if (hasOverrideConfiguration) {
- tmpConfig.updateFrom(key.mOverrideConfiguration);
- }
- r.updateConfiguration(tmpConfig, dm, compat);
- } else {
- r.updateConfiguration(config, dm, compat);
- }
- //Slog.i(TAG, "Updated app resources " + v.getKey()
- // + " " + r + ": " + r.getConfiguration());
+ applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+ defaultDisplayMetrics, key, r);
} else {
- //Slog.i(TAG, "Removing old resources " + v.getKey());
mResourceImpls.removeAt(i);
}
}
+ for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ mResourcesWithLoaders.remove(index);
+ continue;
+ }
+
+ applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+ defaultDisplayMetrics, resourcesWithLoaders.resourcesKey(),
+ resources.getImpl());
+ }
+
return changes != 0;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
+ private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
+ @Nullable CompatibilityInfo compat, Configuration tmpConfig,
+ DisplayMetrics defaultDisplayMetrics, ResourcesKey key, ResourcesImpl resourcesImpl) {
+ if (DEBUG || DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Changing resources "
+ + resourcesImpl + " config to: " + config);
+ }
+ int displayId = key.mDisplayId;
+ boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ DisplayMetrics dm = defaultDisplayMetrics;
+ final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
+ if (!isDefaultDisplay || hasOverrideConfiguration) {
+ tmpConfig.setTo(config);
+
+ // Get new DisplayMetrics based on the DisplayAdjustments given
+ // to the ResourcesImpl. Update a copy if the CompatibilityInfo
+ // changed, because the ResourcesImpl object will handle the
+ // update internally.
+ DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
+ if (compat != null) {
+ daj = new DisplayAdjustments(daj);
+ daj.setCompatibilityInfo(compat);
+ }
+ dm = getDisplayMetrics(displayId, daj);
+
+ if (!isDefaultDisplay) {
+ applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
+ }
+
+ if (hasOverrideConfiguration) {
+ tmpConfig.updateFrom(key.mOverrideConfiguration);
+ }
+ resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
+ } else {
+ resourcesImpl.updateConfiguration(config, dm, compat);
+ }
+ }
+
/**
* Appends the library asset path to any ResourcesImpl object that contains the main
* assetPath.
@@ -1140,7 +1300,7 @@
ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
}
- if (newLibAssets != key.mLibDirs) {
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
updatedResourceKeys.put(impl, new ResourcesKey(
key.mResDir,
key.mSplitResDirs,
@@ -1153,10 +1313,106 @@
}
}
+ final int count = mResourcesWithLoaders.size();
+ for (int index = 0; index < count; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+ if (Objects.equals(key.mResDir, assetPath)) {
+ String[] newLibAssets = key.mLibDirs;
+ for (String libAsset : libAssets) {
+ newLibAssets =
+ ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
+ }
+
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+ updatedResourceKeys.put(resources.getImpl(),
+ new ResourcesKey(
+ key.mResDir,
+ key.mSplitResDirs,
+ key.mOverlayDirs,
+ newLibAssets,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo));
+ }
+ }
+ }
+
redirectResourcesToNewImplLocked(updatedResourceKeys);
}
}
+ /**
+ * Mark a {@link Resources} as containing a {@link ResourceLoader}.
+ *
+ * This removes its {@link ResourcesImpl} from the shared pool and creates it a new one. It is
+ * then tracked separately, kept unique, and restored properly across {@link Activity}
+ * recreations.
+ */
+ public void registerForLoaders(Resources resources) {
+ synchronized (this) {
+ boolean found = false;
+ IBinder activityToken = null;
+
+ // Remove the Resources from the reference list as it's now tracked separately
+ int size = mResourceReferences.size();
+ for (int index = 0; index < size; index++) {
+ WeakReference<Resources> reference = mResourceReferences.get(index);
+ if (reference.get() == resources) {
+ mResourceReferences.remove(index);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Do the same removal for any Activity Resources
+ for (Map.Entry<IBinder, ActivityResources> entry :
+ mActivityResourceReferences.entrySet()) {
+ ArrayList<WeakReference<Resources>> activityResourcesList =
+ entry.getValue().activityResources;
+ final int resCount = activityResourcesList.size();
+ for (int index = 0; index < resCount; index++) {
+ WeakReference<Resources> reference = activityResourcesList.get(index);
+ if (reference.get() == resources) {
+ activityToken = entry.getKey();
+ activityResourcesList.remove(index);
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ throw new IllegalArgumentException("Resources " + resources
+ + " registered for loaders but was not previously tracked by"
+ + " ResourcesManager");
+ }
+
+ ResourcesKey key = findKeyForResourceImplLocked(resources.getImpl());
+ ResourcesImpl impl = createResourcesImpl(key);
+
+ if (mResourcesWithLoaders == Collections.EMPTY_LIST) {
+ mResourcesWithLoaders = Collections.synchronizedList(new ArrayList<>());
+ }
+
+ mResourcesWithLoaders.add(new ResourcesWithLoaders(resources, key, activityToken));
+
+ // Set the new Impl, which is now guaranteed to be unique per Resources object
+ resources.setImpl(impl);
+ }
+ }
+
// TODO(adamlesinski): Make this accept more than just overlay directories.
final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
@Nullable final String[] oldPaths) {
@@ -1201,6 +1457,32 @@
}
}
+ final int count = mResourcesWithLoaders.size();
+ for (int index = 0; index < count; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+
+ if (key.mResDir == null
+ || key.mResDir.equals(baseCodePath)
+ || ArrayUtils.contains(oldPaths, key.mResDir)) {
+ updatedResourceKeys.put(resources.getImpl(),
+ new ResourcesKey(
+ baseCodePath,
+ copiedSplitDirs,
+ copiedResourceDirs,
+ key.mLibDirs,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo
+ ));
+ }
+ }
+
redirectResourcesToNewImplLocked(updatedResourceKeys);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1250,5 +1532,25 @@
}
}
}
+
+ // Update any references that need to be re-built with loaders. These are intentionally not
+ // part of either of the above lists.
+ final int loaderCount = mResourcesWithLoaders.size();
+ for (int index = loaderCount - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl());
+ if (newKey == null) {
+ continue;
+ }
+
+ resourcesWithLoaders.updateKey(newKey);
+ resourcesWithLoaders.resources()
+ .setImpl(createResourcesImpl(newKey));
+ }
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c3c383c..8ea1ff5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2330,6 +2330,12 @@
"android.app.action.ADMIN_POLICY_COMPLIANCE";
/**
+ * Maximum supported password length. Kind-of arbitrary.
+ * @hide
+ */
+ public static final int MAX_PASSWORD_LENGTH = 16;
+
+ /**
* Return true if the given administrator component is currently active (enabled) in the system.
*
* @param admin The administrator component to check for.
@@ -3233,6 +3239,22 @@
}
/**
+ * Returns minimum PasswordMetrics that satisfies all admin policies.
+ *
+ * @hide
+ */
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.getPasswordMinimumMetrics(userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by an application that is administering the device to set the length of the password
* history. After setting this, the user will not be able to enter a new password that is the
* same as any password in the history. Note that the current password will remain until the
@@ -3415,8 +3437,7 @@
if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
return 0;
}
- // Kind-of arbitrary.
- return 16;
+ return MAX_PASSWORD_LENGTH;
}
/**
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0da5b7a..7d2c54e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -72,6 +72,8 @@
void setPasswordMinimumNonLetter(in ComponentName who, int length, boolean parent);
int getPasswordMinimumNonLetter(in ComponentName who, int userHandle, boolean parent);
+ PasswordMetrics getPasswordMinimumMetrics(int userHandle);
+
void setPasswordHistoryLength(in ComponentName who, int length, boolean parent);
int getPasswordHistoryLength(in ComponentName who, int userHandle, boolean parent);
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 9929855..d9bfde5 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -16,45 +16,65 @@
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LETTERS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LOWER_CASE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_LETTER;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYMBOLS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE;
+import static com.android.internal.widget.PasswordValidationError.TOO_LONG;
+import static com.android.internal.widget.PasswordValidationError.TOO_SHORT;
+import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils.CredentialType;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
/**
* A class that represents the metrics of a credential that are used to decide whether or not a
- * credential meets the requirements. If the credential is a pattern, only quality matters.
+ * credential meets the requirements.
*
* {@hide}
*/
-public class PasswordMetrics implements Parcelable {
+public final class PasswordMetrics implements Parcelable {
+ private static final String TAG = "PasswordMetrics";
+
// Maximum allowed number of repeated or ordered characters in a sequence before we'll
// consider it a complex PIN/password.
public static final int MAX_ALLOWED_SEQUENCE = 3;
- public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ public @CredentialType int credType;
+ // Fields below only make sense when credType is PASSWORD.
public int length = 0;
public int letters = 0;
public int upperCase = 0;
@@ -62,139 +82,62 @@
public int numeric = 0;
public int symbols = 0;
public int nonLetter = 0;
+ public int nonNumeric = 0;
+ // MAX_VALUE is the most relaxed value, any sequence is ok, e.g. 123456789. 4 would forbid it.
+ public int seqLength = Integer.MAX_VALUE;
- public PasswordMetrics() {}
-
- public PasswordMetrics(int quality) {
- this.quality = quality;
+ public PasswordMetrics(int credType) {
+ this.credType = credType;
}
- public PasswordMetrics(int quality, int length) {
- this.quality = quality;
+ public PasswordMetrics(int credType , int length, int letters, int upperCase, int lowerCase,
+ int numeric, int symbols, int nonLetter, int nonNumeric, int seqLength) {
+ this.credType = credType;
this.length = length;
- }
-
- public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase,
- int numeric, int symbols, int nonLetter) {
- this(quality, length);
this.letters = letters;
this.upperCase = upperCase;
this.lowerCase = lowerCase;
this.numeric = numeric;
this.symbols = symbols;
this.nonLetter = nonLetter;
+ this.nonNumeric = nonNumeric;
+ this.seqLength = seqLength;
}
- private PasswordMetrics(Parcel in) {
- quality = in.readInt();
- length = in.readInt();
- letters = in.readInt();
- upperCase = in.readInt();
- lowerCase = in.readInt();
- numeric = in.readInt();
- symbols = in.readInt();
- nonLetter = in.readInt();
- }
-
- /** Returns the min quality allowed by {@code complexityLevel}. */
- public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
- // this would be the quality of the first metrics since mMetrics is sorted in ascending
- // order of quality
- return PasswordComplexityBucket
- .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
- }
-
- /**
- * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
- * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
- * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
- * and {@code complexityLevel}.
- *
- * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
- * more than one set of metrics to meet the minimum complexity requirement and inspecting what
- * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
- * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or
- * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
- * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
- * an alphanumeric password so we would update the min complexity required min length to 6.
- */
- public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
- int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
- boolean requiresLettersOrSymbols) {
- int targetQuality = Math.max(
- userEnteredPasswordQuality,
- getActualRequiredQuality(
- requestedQuality, requiresNumeric, requiresLettersOrSymbols));
- return getTargetQualityMetrics(complexityLevel, targetQuality);
- }
-
- /**
- * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
- * is the same as {@code targetQuality}.
- *
- * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
- * with the min quality at {@code complexityLevel}.
- */
- // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
- @VisibleForTesting
- public static PasswordMetrics getTargetQualityMetrics(
- @PasswordComplexity int complexityLevel, int targetQuality) {
- PasswordComplexityBucket targetBucket =
- PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
- for (PasswordMetrics metrics : targetBucket.mMetrics) {
- if (targetQuality == metrics.quality) {
- return metrics;
- }
- }
- // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
- // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
- // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
- // requested quality is NUMERIC
- return targetBucket.mMetrics[0];
- }
-
- /**
- * Finds out the actual quality requirement based on whether quality is {@link
- * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
- * required.
- */
- @VisibleForTesting
- // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
- public static int getActualRequiredQuality(
- int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
- if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
- return requestedQuality;
- }
-
- // find out actual password quality from complex requirements
- if (requiresNumeric && requiresLettersOrSymbols) {
- return PASSWORD_QUALITY_ALPHANUMERIC;
- }
- if (requiresLettersOrSymbols) {
- return PASSWORD_QUALITY_ALPHABETIC;
- }
- if (requiresNumeric) {
- // cannot specify numeric complex using complex quality so this must be numeric
- return PASSWORD_QUALITY_NUMERIC;
- }
-
- // reaching here means dpm sets quality to complex without specifying any requirements
- return PASSWORD_QUALITY_UNSPECIFIED;
+ private PasswordMetrics(PasswordMetrics other) {
+ this(other.credType, other.length, other.letters, other.upperCase, other.lowerCase,
+ other.numeric, other.symbols, other.nonLetter, other.nonNumeric, other.seqLength);
}
/**
* Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
* if {@code complexityLevel} is not valid.
+ *
+ * TODO: move to PasswordPolicy
*/
@PasswordComplexity
public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
- return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+ switch (complexityLevel) {
+ case PASSWORD_COMPLEXITY_HIGH:
+ case PASSWORD_COMPLEXITY_MEDIUM:
+ case PASSWORD_COMPLEXITY_LOW:
+ case PASSWORD_COMPLEXITY_NONE:
+ return complexityLevel;
+ default:
+ Log.w(TAG, "Invalid password complexity used: " + complexityLevel);
+ return PASSWORD_COMPLEXITY_NONE;
+ }
}
- public boolean isDefault() {
- return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
- && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
- && numeric == 0 && symbols == 0 && nonLetter == 0;
+ private static boolean hasInvalidCharacters(byte[] password) {
+ // Allow non-control Latin-1 characters only.
+ for (byte b : password) {
+ char c = (char) b;
+ if (c < 32 || c > 127) {
+ return true;
+ }
+ }
+ return false;
}
@Override
@@ -204,7 +147,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(quality);
+ dest.writeInt(credType);
dest.writeInt(length);
dest.writeInt(letters);
dest.writeInt(upperCase);
@@ -212,12 +155,25 @@
dest.writeInt(numeric);
dest.writeInt(symbols);
dest.writeInt(nonLetter);
+ dest.writeInt(nonNumeric);
+ dest.writeInt(seqLength);
}
- public static final @android.annotation.NonNull Parcelable.Creator<PasswordMetrics> CREATOR
+ public static final @NonNull Parcelable.Creator<PasswordMetrics> CREATOR
= new Parcelable.Creator<PasswordMetrics>() {
public PasswordMetrics createFromParcel(Parcel in) {
- return new PasswordMetrics(in);
+ int credType = in.readInt();
+ int length = in.readInt();
+ int letters = in.readInt();
+ int upperCase = in.readInt();
+ int lowerCase = in.readInt();
+ int numeric = in.readInt();
+ int symbols = in.readInt();
+ int nonLetter = in.readInt();
+ int nonNumeric = in.readInt();
+ int seqLength = in.readInt();
+ return new PasswordMetrics(credType, length, letters, upperCase, lowerCase, numeric,
+ symbols, nonLetter, nonNumeric, seqLength);
}
public PasswordMetrics[] newArray(int size) {
@@ -226,21 +182,21 @@
};
/**
- * Returnsthe {@code PasswordMetrics} for a given credential.
+ * Returns the {@code PasswordMetrics} for a given credential.
*
* If the credential is a pin or a password, equivalent to {@link #computeForPassword(byte[])}.
* {@code credential} cannot be null when {@code type} is
* {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
*/
- public static PasswordMetrics computeForCredential(
- @CredentialType int type, byte[] credential) {
- if (type == CREDENTIAL_TYPE_PASSWORD) {
- Preconditions.checkNotNull(credential, "credential cannot be null");
- return PasswordMetrics.computeForPassword(credential);
- } else if (type == CREDENTIAL_TYPE_PATTERN) {
- return new PasswordMetrics(PASSWORD_QUALITY_SOMETHING);
- } else /* if (type == CREDENTIAL_TYPE_NONE) */ {
- return new PasswordMetrics(PASSWORD_QUALITY_UNSPECIFIED);
+ public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
+ if (credential.isPassword()) {
+ return PasswordMetrics.computeForPassword(credential.getCredential());
+ } else if (credential.isPattern()) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ } else if (credential.isNone()) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ } else {
+ throw new IllegalArgumentException("Unknown credential type " + credential.getType());
}
}
@@ -255,16 +211,19 @@
int numeric = 0;
int symbols = 0;
int nonLetter = 0;
+ int nonNumeric = 0;
final int length = password.length;
for (byte b : password) {
switch (categoryChar((char) b)) {
case CHAR_LOWER_CASE:
letters++;
lowerCase++;
+ nonNumeric++;
break;
case CHAR_UPPER_CASE:
letters++;
upperCase++;
+ nonNumeric++;
break;
case CHAR_DIGIT:
numeric++;
@@ -273,53 +232,14 @@
case CHAR_SYMBOL:
symbols++;
nonLetter++;
+ nonNumeric++;
break;
}
}
- // Determine the quality of the password
- final boolean hasNumeric = numeric > 0;
- final boolean hasNonNumeric = (letters + symbols) > 0;
- final int quality;
- if (hasNonNumeric && hasNumeric) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- } else if (hasNonNumeric) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
- } else if (hasNumeric) {
- quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE
- ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- } else {
- quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- }
-
- return new PasswordMetrics(
- quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter);
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof PasswordMetrics)) {
- return false;
- }
- PasswordMetrics o = (PasswordMetrics) other;
- return this.quality == o.quality
- && this.length == o.length
- && this.letters == o.letters
- && this.upperCase == o.upperCase
- && this.lowerCase == o.lowerCase
- && this.numeric == o.numeric
- && this.symbols == o.symbols
- && this.nonLetter == o.nonLetter;
- }
-
- private boolean satisfiesBucket(PasswordMetrics... bucket) {
- for (PasswordMetrics metrics : bucket) {
- if (this.quality == metrics.quality) {
- return this.length >= metrics.length;
- }
- }
- return false;
+ final int seqLength = maxLengthSequence(password);
+ return new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD, length, letters, upperCase, lowerCase,
+ numeric, symbols, nonLetter, nonNumeric, seqLength);
}
/**
@@ -404,108 +324,394 @@
}
}
- /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */
- @PasswordComplexity
- public int determineComplexity() {
- for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
- if (satisfiesBucket(bucket.mMetrics)) {
- return bucket.mComplexityLevel;
- }
+ /**
+ * Returns the weakest metrics that is stricter or equal to all given metrics.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static PasswordMetrics merge(List<PasswordMetrics> metrics) {
+ PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ for (PasswordMetrics m : metrics) {
+ result.maxWith(m);
}
- return PASSWORD_COMPLEXITY_NONE;
+
+ return result;
}
/**
- * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
+ * Makes current metric at least as strong as {@code other} in every criterion.
+ *
+ * TODO: move to PasswordPolicy
*/
- private static class PasswordComplexityBucket {
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket HIGH =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_HIGH,
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 8),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
- 6));
+ private void maxWith(PasswordMetrics other) {
+ credType = Math.max(credType, other.credType);
+ if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ return;
+ }
+ length = Math.max(length, other.length);
+ letters = Math.max(letters, other.letters);
+ upperCase = Math.max(upperCase, other.upperCase);
+ lowerCase = Math.max(lowerCase, other.lowerCase);
+ numeric = Math.max(numeric, other.numeric);
+ symbols = Math.max(symbols, other.symbols);
+ nonLetter = Math.max(nonLetter, other.nonLetter);
+ nonNumeric = Math.max(nonNumeric, other.nonNumeric);
+ seqLength = Math.min(seqLength, other.seqLength);
+ }
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket MEDIUM =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_MEDIUM,
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 4),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
- 4));
+ /**
+ * Returns minimum password quality for a given complexity level.
+ *
+ * TODO: this function is used for determining allowed credential types, so it should return
+ * credential type rather than 'quality'.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static int complexityLevelToMinQuality(int complexity) {
+ switch (complexity) {
+ case PASSWORD_COMPLEXITY_HIGH:
+ case PASSWORD_COMPLEXITY_MEDIUM:
+ return PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ case PASSWORD_COMPLEXITY_LOW:
+ return PASSWORD_QUALITY_SOMETHING;
+ case PASSWORD_COMPLEXITY_NONE:
+ default:
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ }
+ }
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket LOW =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
-
- /**
- * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
- */
- private static final PasswordComplexityBucket NONE =
- new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics());
-
- /** Array containing all buckets from high to low. */
- private static final PasswordComplexityBucket[] BUCKETS =
- new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW};
-
- @PasswordComplexity
- private final int mComplexityLevel;
- private final PasswordMetrics[] mMetrics;
-
- /**
- * @param metricsArray must be sorted in ascending order of {@link #quality}.
- */
- private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
- PasswordMetrics... metricsArray) {
- int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
- for (PasswordMetrics metrics : metricsArray) {
- if (metrics.quality < previousQuality) {
- throw new IllegalArgumentException("metricsArray must be sorted in ascending"
- + " order of quality");
- }
- previousQuality = metrics.quality;
+ /**
+ * Enum representing requirements for each complexity level.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private enum ComplexityBucket {
+ // Keep ordered high -> low.
+ BUCKET_HIGH(PASSWORD_COMPLEXITY_HIGH) {
+ @Override
+ boolean canHaveSequence() {
+ return false;
}
- this.mMetrics = metricsArray;
- this.mComplexityLevel = complexityLevel;
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return containsNonNumeric ? 6 : 8;
+ }
+ @Override
+ boolean allowsNumericPassword() {
+ return false;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType == CREDENTIAL_TYPE_PASSWORD;
+ }
+ },
+ BUCKET_MEDIUM(PASSWORD_COMPLEXITY_MEDIUM) {
+ @Override
+ boolean canHaveSequence() {
+ return false;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 4;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return false;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType == CREDENTIAL_TYPE_PASSWORD;
+ }
+ },
+ BUCKET_LOW(PASSWORD_COMPLEXITY_LOW) {
+ @Override
+ boolean canHaveSequence() {
+ return true;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 0;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return true;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType != CREDENTIAL_TYPE_NONE;
+ }
+ },
+ BUCKET_NONE(PASSWORD_COMPLEXITY_NONE) {
+ @Override
+ boolean canHaveSequence() {
+ return true;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 0;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return true;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return true;
+ }
+ };
+
+ int mComplexityLevel;
+
+ abstract boolean canHaveSequence();
+ abstract int getMinimumLength(boolean containsNonNumeric);
+ abstract boolean allowsNumericPassword();
+ abstract boolean allowsCredType(int credType);
+
+ ComplexityBucket(int complexityLevel) {
+ this.mComplexityLevel = complexityLevel;
}
- /** Returns the bucket that {@code complexityLevel} represents. */
- private static PasswordComplexityBucket complexityLevelToBucket(
- @PasswordComplexity int complexityLevel) {
- for (PasswordComplexityBucket bucket : BUCKETS) {
+ static ComplexityBucket forComplexity(int complexityLevel) {
+ for (ComplexityBucket bucket : values()) {
if (bucket.mComplexityLevel == complexityLevel) {
return bucket;
}
}
- return NONE;
+ throw new IllegalArgumentException("Invalid complexity level: " + complexityLevel);
}
}
+
+ /**
+ * Returns whether current metrics satisfies a given complexity bucket.
+ *
+ * TODO: move inside ComplexityBucket.
+ */
+ private boolean satisfiesBucket(ComplexityBucket bucket) {
+ if (!bucket.allowsCredType(credType)) {
+ return false;
+ }
+ if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ return true;
+ }
+ return (bucket.canHaveSequence() || seqLength <= MAX_ALLOWED_SEQUENCE)
+ && length >= bucket.getMinimumLength(nonNumeric > 0 /* hasNonNumeric */);
+ }
+
+ /**
+ * Returns the maximum complexity level satisfied by password with this metrics.
+ *
+ * TODO: move inside ComplexityBucket.
+ */
+ public int determineComplexity() {
+ for (ComplexityBucket bucket : ComplexityBucket.values()) {
+ if (satisfiesBucket(bucket)) {
+ return bucket.mComplexityLevel;
+ }
+ }
+ throw new IllegalStateException("Failed to figure out complexity for a given metrics");
+ }
+
+ /**
+ * Validates password against minimum metrics and complexity.
+ *
+ * @param adminMetrics - minimum metrics to satisfy admin requirements.
+ * @param minComplexity - minimum complexity imposed by the requester.
+ * @param isPin - whether it is PIN that should be only digits
+ * @param password - password to validate.
+ * @return a list of password validation errors. An empty list means the password is OK.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static List<PasswordValidationError> validatePassword(
+ PasswordMetrics adminMetrics, int minComplexity, boolean isPin, byte[] password) {
+
+ if (hasInvalidCharacters(password)) {
+ return Collections.singletonList(
+ new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
+ }
+
+ final PasswordMetrics enteredMetrics = computeForPassword(password);
+ return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics);
+ }
+
+ /**
+ * Validates password metrics against minimum metrics and complexity
+ *
+ * @param adminMetrics - minimum metrics to satisfy admin requirements.
+ * @param minComplexity - minimum complexity imposed by the requester.
+ * @param isPin - whether it is PIN that should be only digits
+ * @param actualMetrics - metrics for password to validate.
+ * @return a list of password validation errors. An empty list means the password is OK.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static List<PasswordValidationError> validatePasswordMetrics(
+ PasswordMetrics adminMetrics, int minComplexity, boolean isPin,
+ PasswordMetrics actualMetrics) {
+ final ComplexityBucket bucket = ComplexityBucket.forComplexity(minComplexity);
+
+ // Make sure credential type is satisfactory.
+ // TODO: stop relying on credential type ordering.
+ if (actualMetrics.credType < adminMetrics.credType
+ || !bucket.allowsCredType(actualMetrics.credType)) {
+ return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
+ }
+ // TODO: this needs to be modified if CREDENTIAL_TYPE_PIN is added.
+ if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD) {
+ return Collections.emptyList(); // Nothing to check for pattern or none.
+ }
+
+ if (isPin && actualMetrics.nonNumeric > 0) {
+ return Collections.singletonList(
+ new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
+ }
+
+ final ArrayList<PasswordValidationError> result = new ArrayList<>();
+ if (actualMetrics.length > MAX_PASSWORD_LENGTH) {
+ result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH));
+ }
+
+ final PasswordMetrics minMetrics = applyComplexity(adminMetrics, isPin, bucket);
+
+ // Clamp required length between maximum and minimum valid values.
+ minMetrics.length = Math.min(MAX_PASSWORD_LENGTH,
+ Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE));
+ minMetrics.removeOverlapping();
+
+ comparePasswordMetrics(minMetrics, actualMetrics, result);
+
+ return result;
+ }
+
+ /**
+ * TODO: move to PasswordPolicy
+ */
+ private static void comparePasswordMetrics(PasswordMetrics minMetrics,
+ PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) {
+ if (actualMetrics.length < minMetrics.length) {
+ result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length));
+ }
+ if (actualMetrics.letters < minMetrics.letters) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters));
+ }
+ if (actualMetrics.upperCase < minMetrics.upperCase) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_UPPER_CASE, minMetrics.upperCase));
+ }
+ if (actualMetrics.lowerCase < minMetrics.lowerCase) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_LOWER_CASE, minMetrics.lowerCase));
+ }
+ if (actualMetrics.numeric < minMetrics.numeric) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_DIGITS, minMetrics.numeric));
+ }
+ if (actualMetrics.symbols < minMetrics.symbols) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_SYMBOLS, minMetrics.symbols));
+ }
+ if (actualMetrics.nonLetter < minMetrics.nonLetter) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_NON_LETTER, minMetrics.nonLetter));
+ }
+ if (actualMetrics.nonNumeric < minMetrics.nonNumeric) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_NON_DIGITS, minMetrics.nonNumeric));
+ }
+ if (actualMetrics.seqLength > minMetrics.seqLength) {
+ result.add(new PasswordValidationError(CONTAINS_SEQUENCE, 0));
+ }
+ }
+
+ /**
+ * Drop requirements that are superseded by others, e.g. if it is required to have 5 upper case
+ * letters and 5 lower case letters, there is no need to require minimum number of letters to
+ * be 10 since it will be fulfilled once upper and lower case requirements are fulfilled.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private void removeOverlapping() {
+ // upperCase + lowerCase can override letters
+ final int indirectLetters = upperCase + lowerCase;
+
+ // numeric + symbols can override nonLetter
+ final int indirectNonLetter = numeric + symbols;
+
+ // letters + symbols can override nonNumeric
+ final int effectiveLetters = Math.max(letters, indirectLetters);
+ final int indirectNonNumeric = effectiveLetters + symbols;
+
+ // letters + nonLetters can override length
+ // numeric + nonNumeric can also override length, so max it with previous.
+ final int effectiveNonLetter = Math.max(nonLetter, indirectNonLetter);
+ final int effectiveNonNumeric = Math.max(nonNumeric, indirectNonNumeric);
+ final int indirectLength = Math.max(effectiveLetters + effectiveNonLetter,
+ numeric + effectiveNonNumeric);
+
+ if (indirectLetters >= letters) {
+ letters = 0;
+ }
+ if (indirectNonLetter >= nonLetter) {
+ nonLetter = 0;
+ }
+ if (indirectNonNumeric >= nonNumeric) {
+ nonNumeric = 0;
+ }
+ if (indirectLength >= length) {
+ length = 0;
+ }
+ }
+
+ /**
+ * Combine minimum metrics, set by admin, complexity set by the requester and actual entered
+ * password metrics to get resulting minimum metrics that the password has to satisfy. Always
+ * returns a new PasswordMetrics object.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private static PasswordMetrics applyComplexity(
+ PasswordMetrics adminMetrics, boolean isPin, ComplexityBucket bucket) {
+ final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics);
+
+ if (!bucket.canHaveSequence()) {
+ minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE);
+ }
+
+ minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin));
+
+ if (!isPin && !bucket.allowsNumericPassword()) {
+ minMetrics.nonNumeric = Math.max(minMetrics.nonNumeric, 1);
+ }
+
+ return minMetrics;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final PasswordMetrics that = (PasswordMetrics) o;
+ return credType == that.credType
+ && length == that.length
+ && letters == that.letters
+ && upperCase == that.upperCase
+ && lowerCase == that.lowerCase
+ && numeric == that.numeric
+ && symbols == that.symbols
+ && nonLetter == that.nonLetter
+ && nonNumeric == that.nonNumeric
+ && seqLength == that.seqLength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(credType, length, letters, upperCase, lowerCase, numeric, symbols,
+ nonLetter, nonNumeric, seqLength);
+ }
}
diff --git a/core/java/android/app/admin/PasswordPolicy.java b/core/java/android/app/admin/PasswordPolicy.java
new file mode 100644
index 0000000..13f11ad
--- /dev/null
+++ b/core/java/android/app/admin/PasswordPolicy.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 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.admin;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+/**
+ * {@hide}
+ */
+public class PasswordPolicy {
+ public static final int DEF_MINIMUM_LENGTH = 0;
+ public static final int DEF_MINIMUM_LETTERS = 1;
+ public static final int DEF_MINIMUM_UPPER_CASE = 0;
+ public static final int DEF_MINIMUM_LOWER_CASE = 0;
+ public static final int DEF_MINIMUM_NUMERIC = 1;
+ public static final int DEF_MINIMUM_SYMBOLS = 1;
+ public static final int DEF_MINIMUM_NON_LETTER = 0;
+
+ public int quality = PASSWORD_QUALITY_UNSPECIFIED;
+ public int length = DEF_MINIMUM_LENGTH;
+ public int letters = DEF_MINIMUM_LETTERS;
+ public int upperCase = DEF_MINIMUM_UPPER_CASE;
+ public int lowerCase = DEF_MINIMUM_LOWER_CASE;
+ public int numeric = DEF_MINIMUM_NUMERIC;
+ public int symbols = DEF_MINIMUM_SYMBOLS;
+ public int nonLetter = DEF_MINIMUM_NON_LETTER;
+
+ /**
+ * Returns a minimum password metrics that the password should have to satisfy current policy.
+ */
+ public PasswordMetrics getMinMetrics() {
+ if (quality == PASSWORD_QUALITY_UNSPECIFIED) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ } else if (quality == PASSWORD_QUALITY_BIOMETRIC_WEAK
+ || quality == PASSWORD_QUALITY_SOMETHING) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ } // quality is NUMERIC or stronger.
+
+ PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ result.length = length;
+
+ if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+ result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
+ } else if (quality == PASSWORD_QUALITY_ALPHABETIC) {
+ result.nonNumeric = 1;
+ } else if (quality == PASSWORD_QUALITY_ALPHANUMERIC) {
+ result.numeric = 1;
+ result.nonNumeric = 1;
+ } else if (quality == PASSWORD_QUALITY_COMPLEX) {
+ result.numeric = numeric;
+ result.letters = letters;
+ result.upperCase = upperCase;
+ result.lowerCase = lowerCase;
+ result.nonLetter = nonLetter;
+ result.symbols = symbols;
+ }
+ return result;
+ }
+}
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 955093d..90ecce2 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -390,6 +390,8 @@
}
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+ new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
return null;
diff --git a/core/java/android/app/timedetector/TimeSignal.java b/core/java/android/app/timedetector/TimeSignal.java
index da21794..b494260 100644
--- a/core/java/android/app/timedetector/TimeSignal.java
+++ b/core/java/android/app/timedetector/TimeSignal.java
@@ -56,8 +56,7 @@
private static TimeSignal createFromParcel(Parcel in) {
String sourceId = in.readString();
- TimestampedValue<Long> utcTime =
- TimestampedValue.readFromParcel(in, null /* classLoader */, Long.class);
+ TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
return new TimeSignal(sourceId, utcTime);
}
@@ -69,7 +68,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mSourceId);
- TimestampedValue.writeToParcel(dest, mUtcTime);
+ dest.writeParcelable(mUtcTime, 0);
}
@NonNull
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2dde3ae..e7e278f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1559,7 +1559,14 @@
* @see Environment#getExternalStorageState(File)
* @see Environment#isExternalStorageEmulated(File)
* @see Environment#isExternalStorageRemovable(File)
+ * @deprecated These directories still exist and are scanned, but developers
+ * are encouraged to migrate to inserting content into a
+ * {@link MediaStore} collection directly, as any app can
+ * contribute new media to {@link MediaStore} with no
+ * permissions required, starting in
+ * {@link android.os.Build.VERSION_CODES#Q}.
*/
+ @Deprecated
public abstract File[] getExternalMediaDirs();
/**
@@ -5237,7 +5244,7 @@
@SystemApi
@TestApi
@NonNull
- public Context createContextAsUser(@NonNull UserHandle user) {
+ public Context createContextAsUser(@NonNull UserHandle user, @CreatePackageOptions int flags) {
if (Build.IS_ENG) {
throw new IllegalStateException("createContextAsUser not overridden!");
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index f7cd51e..7993ea1 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -885,8 +885,8 @@
/** @hide */
@Override
- public Context createContextAsUser(UserHandle user) {
- return mBase.createContextAsUser(user);
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
+ return mBase.createContextAsUser(user, flags);
}
/** @hide */
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 853e818..a2f8886 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -166,9 +166,8 @@
}
/**
- * Returns information about all overlays for the given target package for
- * the specified user. The returned list is ordered according to the
- * overlay priority with the highest priority at the end of the list.
+ * Clear part of the overlay manager's internal cache of PackageInfo
+ * objects. Only intended for testing.
*
* @param targetPackageName The name of the target package.
* @param user The user to get the OverlayInfos for.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 19d8edf..1d78e2c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -682,6 +682,8 @@
String getWellbeingPackageName();
+ String[] getTelephonyPackageNames();
+
String getAppPredictionServicePackageName();
String getSystemCaptionsServicePackageName();
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 69ce3bd..edc66c5 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -349,14 +349,7 @@
*/
public int createSession(@NonNull SessionParams params) throws IOException {
try {
- final String installerPackage;
- if (params.installerPackageName == null) {
- installerPackage = mInstallerPackageName;
- } else {
- installerPackage = params.installerPackageName;
- }
-
- return mInstaller.createSession(params, installerPackage, mUserId);
+ return mInstaller.createSession(params, mInstallerPackageName, mUserId);
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9513ce8..7509065 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2858,6 +2858,14 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device does not have slices implementation.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SLICES_DISABLED = "android.software.slices_disabled";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device supports device-unique Keystore attestations. Only available on devices that
* also support {@link #FEATURE_STRONGBOX_KEYSTORE}, and can only be used by device owner
* apps (see {@link android.app.admin.DevicePolicyManager#generateKeyPair}).
@@ -7416,6 +7424,18 @@
}
/**
+ * @return the system defined telephony package names, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public String[] getTelephonyPackageNames() {
+ throw new UnsupportedOperationException(
+ "getTelephonyPackageNames not implemented in subclass");
+ }
+
+ /**
* @return the system defined content capture service package name, or null if there's none.
*
* @hide
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0b157fa..cf21e96 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -577,8 +577,6 @@
*/
public interface Callback {
boolean hasFeature(String feature);
- String[] getOverlayPaths(String targetPackageName, String targetPath);
- String[] getOverlayApks(String targetPackageName);
}
/**
@@ -595,14 +593,6 @@
@Override public boolean hasFeature(String feature) {
return mPm.hasSystemFeature(feature);
}
-
- @Override public String[] getOverlayPaths(String targetPackageName, String targetPath) {
- return null;
- }
-
- @Override public String[] getOverlayApks(String targetPackageName) {
- return null;
- }
}
/**
@@ -1195,19 +1185,7 @@
}
final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
- Package p = fromCacheEntry(bytes);
- if (mCallback != null) {
- String[] overlayApks = mCallback.getOverlayApks(p.packageName);
- if (overlayApks != null && overlayApks.length > 0) {
- for (String overlayApk : overlayApks) {
- // If a static RRO is updated, return null.
- if (!isCacheUpToDate(new File(overlayApk), cacheFile)) {
- return null;
- }
- }
- }
- }
- return p;
+ return fromCacheEntry(bytes);
} catch (Throwable e) {
Slog.w(TAG, "Error reading package cache: ", e);
@@ -1381,7 +1359,7 @@
final Resources res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
- final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
+ final Package pkg = parseBaseApk(res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
@@ -1944,7 +1922,6 @@
* need to consider whether they should be supported by split APKs and child
* packages.
*
- * @param apkPath The package apk file path
* @param res The resources from which to resolve values
* @param parser The manifest parser
* @param flags Flags how to parse
@@ -1954,8 +1931,7 @@
* @throws XmlPullParserException
* @throws IOException
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
+ private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
@@ -1975,15 +1951,6 @@
return null;
}
- if (mCallback != null) {
- String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
- if (overlayPaths != null && overlayPaths.length > 0) {
- for (String overlayPath : overlayPaths) {
- res.getAssets().addOverlayPath(overlayPath);
- }
- }
- }
-
final Package pkg = new Package(pkgName);
TypedArray sa = res.obtainAttributes(parser,
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index dd5c6a5..aa6f58e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -237,6 +237,28 @@
@TestApi
public static final int PROTECTION_FLAG_APP_PREDICTOR = 0x200000;
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>telephony</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int PROTECTION_FLAG_TELEPHONY = 0x400000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>wifi</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int PROTECTION_FLAG_WIFI = 0x800000;
+
/** @hide */
@IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
PROTECTION_FLAG_PRIVILEGED,
@@ -258,6 +280,8 @@
PROTECTION_FLAG_CONFIGURATOR,
PROTECTION_FLAG_INCIDENT_REPORT_APPROVER,
PROTECTION_FLAG_APP_PREDICTOR,
+ PROTECTION_FLAG_TELEPHONY,
+ PROTECTION_FLAG_WIFI,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -501,6 +525,12 @@
if ((level & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0) {
protLevel += "|appPredictor";
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0) {
+ protLevel += "|telephony";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_WIFI) != 0) {
+ protLevel += "|wifi";
+ }
return protLevel;
}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index a35ad56..de1d514 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -16,7 +16,10 @@
package android.content.res;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
+import android.content.res.loader.ResourcesProvider;
+import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -36,10 +39,14 @@
*/
public final class ApkAssets {
@GuardedBy("this") private final long mNativePtr;
+
+ @Nullable
@GuardedBy("this") private final StringBlock mStringBlock;
@GuardedBy("this") private boolean mOpen = true;
+ private final boolean mForLoader;
+
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -48,7 +55,8 @@
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
- return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -61,7 +69,8 @@
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
throws IOException {
- return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
+ return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -76,7 +85,8 @@
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
boolean forceSharedLibrary) throws IOException {
- return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
+ return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -96,7 +106,8 @@
public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
@NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
throws IOException {
- return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
+ return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/,
+ false /*forLoader*/);
}
/**
@@ -110,21 +121,90 @@
*/
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
throws IOException {
- return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
+ return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
- private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ /**
+ * Creates a new ApkAssets instance from the given path on disk for use with a
+ * {@link ResourcesProvider}.
+ *
+ * @param path The path to an APK on disk.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadApkForLoader(@NonNull String path)
throws IOException {
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/,
+ false /*overlay*/, false /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor for use with a
+ * {@link ResourcesProvider}.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable APK.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ @NonNull
+ public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException {
+ return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+ false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor representing an ARSC
+ * for use with a {@link ResourcesProvider}.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable .arsc.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadArscForLoader(@NonNull FileDescriptor fd)
+ throws IOException {
+ return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+ false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
+ * is required for a lot of APIs, and it's easier to have a non-null reference rather than
+ * tracking a separate identifier.
+ */
+ @NonNull
+ public static ApkAssets loadEmptyForLoader() {
+ return new ApkAssets(true);
+ }
+
+ private ApkAssets(boolean forLoader) {
+ mForLoader = forLoader;
+ mNativePtr = nativeLoadEmpty(forLoader);
+ mStringBlock = null;
+ }
+
+ private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay,
+ boolean arscOnly, boolean forLoader) throws IOException {
+ mForLoader = forLoader;
Preconditions.checkNotNull(path, "path");
- mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
+ mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader)
+ : nativeLoad(path, system, forceSharedLib, overlay, forLoader);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
- boolean forceSharedLib) throws IOException {
+ boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException {
+ mForLoader = forLoader;
Preconditions.checkNotNull(fd, "fd");
Preconditions.checkNotNull(friendlyName, "friendlyName");
- mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
+ mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader)
+ : nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
@@ -136,11 +216,19 @@
}
CharSequence getStringFromPool(int idx) {
+ if (mStringBlock == null) {
+ return null;
+ }
+
synchronized (this) {
return mStringBlock.get(idx);
}
}
+ public boolean isForLoader() {
+ return mForLoader;
+ }
+
/**
* Retrieve a parser for a compiled XML file. This is associated with a single APK and
* <em>NOT</em> a full AssetManager. This means that shared-library references will not be
@@ -192,18 +280,26 @@
synchronized (this) {
if (mOpen) {
mOpen = false;
- mStringBlock.close();
+ if (mStringBlock != null) {
+ mStringBlock.close();
+ }
nativeDestroy(mNativePtr);
}
}
}
- private static native long nativeLoad(
- @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ private static native long nativeLoad(@NonNull String path, boolean system,
+ boolean forceSharedLib, boolean overlay, boolean forLoader)
throws IOException;
private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
- @NonNull String friendlyName, boolean system, boolean forceSharedLib)
+ @NonNull String friendlyName, boolean system, boolean forceSharedLib,
+ boolean forLoader)
throws IOException;
+ private static native long nativeLoadArsc(@NonNull String path, boolean forLoader)
+ throws IOException;
+ private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, boolean forLoader) throws IOException;
+ private static native long nativeLoadEmpty(boolean forLoader);
private static native void nativeDestroy(long ptr);
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native long nativeGetStringBlock(long ptr);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7d6dc97..23e7720 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,9 +27,13 @@
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
import android.os.ParcelFileDescriptor;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -39,15 +43,19 @@
import libcore.io.IoUtils;
import java.io.BufferedReader;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.FileLock;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -110,6 +118,13 @@
@GuardedBy("this") private int mNumRefs = 1;
@GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
+ private ResourceLoaderManager mResourceLoaderManager;
+
+ /** @hide */
+ public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) {
+ mResourceLoaderManager = resourceLoaderManager;
+ }
+
/**
* A Builder class that helps create an AssetManager with only a single invocation of
* {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. Without using this builder,
@@ -507,7 +522,7 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
@@ -554,7 +569,7 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ return getPooledStringForCookie(cookie, outValue.data);
}
return outValue.coerceToString();
}
@@ -632,7 +647,7 @@
int cookie = rawInfoArray[i];
int index = rawInfoArray[i + 1];
retArray[j] = (index >= 0 && cookie > 0)
- ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
+ ? getPooledStringForCookie(cookie, index) : null;
}
return retArray;
}
@@ -688,7 +703,7 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
@@ -753,6 +768,7 @@
*
* @hide
*/
+ @TestApi
public void setResourceResolutionLoggingEnabled(boolean enabled) {
synchronized (this) {
ensureValidLocked();
@@ -768,6 +784,7 @@
*
* @hide
*/
+ @TestApi
public @Nullable String getLastResourceResolution() {
synchronized (this) {
ensureValidLocked();
@@ -814,6 +831,13 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ String path = Paths.get("assets", fileName).toString();
+ InputStream inputStream = searchLoaders(0, path, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+
final long asset = nativeOpenAsset(mObject, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -838,6 +862,13 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ String path = Paths.get("assets", fileName).toString();
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path);
+ if (fileDescriptor != null) {
+ return fileDescriptor;
+ }
+
final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
if (pfd == null) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -931,6 +962,12 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ InputStream inputStream = searchLoaders(cookie, fileName, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+
final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -970,6 +1007,12 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+ if (fileDescriptor != null) {
+ return fileDescriptor;
+ }
+
final ParcelFileDescriptor pfd =
nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
if (pfd == null) {
@@ -1031,7 +1074,16 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
- final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+
+ final long xmlBlock;
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+ if (fileDescriptor != null) {
+ xmlBlock = nativeOpenXmlAssetFd(mObject, cookie,
+ fileDescriptor.getFileDescriptor());
+ } else {
+ xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+ }
+
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
@@ -1041,6 +1093,85 @@
}
}
+ private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
+ throws IOException {
+ if (mResourceLoaderManager == null) {
+ return null;
+ }
+
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+ mResourceLoaderManager.getInternalList();
+
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ if (cookie == 0) {
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ try {
+ InputStream inputStream = pair.first.loadAsset(fileName, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
+ }
+
+ return null;
+ }
+
+ ApkAssets apkAssets = mApkAssets[cookie - 1];
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ return pair.first.loadAsset(fileName, accessMode);
+ }
+ }
+
+ return null;
+ }
+
+ private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
+ throws IOException {
+ if (mResourceLoaderManager == null) {
+ return null;
+ }
+
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+ mResourceLoaderManager.getInternalList();
+
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ if (cookie == 0) {
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ try {
+ ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
+ }
+
+ return null;
+ }
+
+ ApkAssets apkAssets = mApkAssets[cookie - 1];
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ return null;
+ }
+ }
+ return null;
+ }
+
void xmlBlockGone(int id) {
synchronized (this) {
decRefsLocked(id);
@@ -1296,7 +1427,7 @@
*
* <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
- * parsed using {@link java.util.Locale#forLanguageTag(String)}.
+ * parsed using {@link Locale#forLanguageTag(String)}.
*
* <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
* are of the form {@code ll_CC} where {@code ll} is a two letter language code,
@@ -1439,6 +1570,8 @@
private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
@NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
+ private static native long nativeOpenXmlAssetFd(long ptr, int cookie,
+ @NonNull FileDescriptor fileDescriptor);
// Primitive resource native methods.
private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index d7e4e14..2698c2d 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -30,6 +30,7 @@
import android.annotation.DrawableRes;
import android.annotation.FontRes;
import android.annotation.FractionRes;
+import android.annotation.IntRange;
import android.annotation.IntegerRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
@@ -41,8 +42,12 @@
import android.annotation.StyleableRes;
import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
+import android.app.ResourcesManager;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
import android.graphics.Movie;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
@@ -54,13 +59,16 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import android.util.TypedValue;
import android.view.DisplayAdjustments;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.XmlUtils;
@@ -71,6 +79,8 @@
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -133,6 +143,11 @@
@UnsupportedAppUsage
final ClassLoader mClassLoader;
+ private final Object mResourceLoaderLock = new Object();
+
+ @GuardedBy("mResourceLoaderLock")
+ private ResourceLoaderManager mResourceLoaderManager;
+
/**
* WeakReferences to Themes that were constructed from this Resources object.
* We keep track of these in case our underlying implementation is changed, in which case
@@ -148,6 +163,8 @@
private static final int MIN_THEME_REFS_FLUSH_SIZE = 32;
private int mThemeRefsNextFlushSize = MIN_THEME_REFS_FLUSH_SIZE;
+ private int mBaseApkAssetsSize;
+
/**
* Returns the most appropriate default theme for the specified target SDK version.
* <ul>
@@ -283,8 +300,15 @@
return;
}
+ mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
mResourcesImpl = impl;
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager != null) {
+ mResourceLoaderManager.onImplUpdate(mResourcesImpl);
+ }
+ }
+
// Create new ThemeImpls that are identical to the ones we have.
synchronized (mThemeRefs) {
final int count = mThemeRefs.size();
@@ -903,7 +927,7 @@
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValueForDensity(id, density, value, true);
- return impl.loadDrawable(this, value, id, density, theme);
+ return loadDrawable(value, id, density, theme);
} finally {
releaseTempTypedValue(value);
}
@@ -913,6 +937,14 @@
@UnsupportedAppUsage
Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
throws NotFoundException {
+ ResourceLoader loader = findLoader(value.assetCookie);
+ if (loader != null) {
+ Drawable drawable = loader.loadDrawable(value, id, density, theme);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
@@ -2280,7 +2312,7 @@
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
- return impl.loadXmlResourceParser(value.string.toString(), id,
+ return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
@@ -2304,6 +2336,14 @@
@UnsupportedAppUsage
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
String type) throws NotFoundException {
+ ResourceLoader loader = findLoader(assetCookie);
+ if (loader != null) {
+ XmlResourceParser xml = loader.loadXmlResourceParser(file, id);
+ if (xml != null) {
+ return xml;
+ }
+ }
+
return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
}
@@ -2329,4 +2369,137 @@
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
+
+ private ResourceLoader findLoader(int assetCookie) {
+ ApkAssets[] apkAssetsArray = mResourcesImpl.getAssets().getApkAssets();
+ int apkAssetsIndex = assetCookie - 1;
+ if (apkAssetsIndex < apkAssetsArray.length && apkAssetsIndex >= 0) {
+ ApkAssets apkAssets = apkAssetsArray[apkAssetsIndex];
+ if (apkAssets.isForLoader()) {
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders;
+ // Since we don't lock the entire resolution path anyways,
+ // only lock here instead of entire method. The list is copied
+ // and effectively a snapshot is used.
+ synchronized (mResourceLoaderLock) {
+ loaders = mResourceLoaderManager.getInternalList();
+ }
+
+ if (!ArrayUtils.isEmpty(loaders)) {
+ int size = loaders.size();
+ for (int index = 0; index < size; index++) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ return pair.first;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return copied list of loaders and providers previously added
+ */
+ @NonNull
+ public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+ synchronized (mResourceLoaderLock) {
+ return mResourceLoaderManager == null
+ ? Collections.emptyList()
+ : mResourceLoaderManager.getLoaders();
+ }
+ }
+
+ /**
+ * Add a custom {@link ResourceLoader} which is added to the paths searched by
+ * {@link AssetManager} when resolving a resource.
+ *
+ * Resources are resolved as if the loader was a resource overlay, meaning the latest
+ * in the list, of equal or better config, is returned.
+ *
+ * {@link ResourcesProvider}s passed in here are not managed and a reference should be held
+ * to remove, re-use, or close them when necessary.
+ *
+ * @param resourceLoader an interface used to resolve file paths for drawables/XML files;
+ * a reference should be kept to remove the loader if necessary
+ * @param resourcesProvider an .apk or .arsc file representation
+ * @param index where to add the loader in the list
+ * @throws IllegalArgumentException if the resourceLoader is already added
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
+ public void addLoader(@NonNull ResourceLoader resourceLoader,
+ @NonNull ResourcesProvider resourcesProvider, @IntRange(from = 0) int index) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ ResourcesManager.getInstance().registerForLoaders(this);
+ mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ }
+
+ mResourceLoaderManager.addLoader(resourceLoader, resourcesProvider, index);
+ }
+ }
+
+ /**
+ * @see #addLoader(ResourceLoader, ResourcesProvider, int).
+ *
+ * Adds to the end of the list.
+ *
+ * @return index the loader was added at
+ */
+ public int addLoader(@NonNull ResourceLoader resourceLoader,
+ @NonNull ResourcesProvider resourcesProvider) {
+ synchronized (mResourceLoaderLock) {
+ int index = getLoaders().size();
+ addLoader(resourceLoader, resourcesProvider, index);
+ return index;
+ }
+ }
+
+ /**
+ * Remove a loader previously added by
+ * {@link #addLoader(ResourceLoader, ResourcesProvider, int)}
+ *
+ * The caller maintains responsibility for holding a reference to the matching
+ * {@link ResourcesProvider} and closing it after this method has been called.
+ *
+ * @param resourceLoader the same reference passed into [addLoader
+ * @return the index the loader was at in the list, or -1 if the loader was not found
+ */
+ public int removeLoader(@NonNull ResourceLoader resourceLoader) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ return -1;
+ }
+
+ return mResourceLoaderManager.removeLoader(resourceLoader);
+ }
+ }
+
+ /**
+ * Swap the current set of loaders. Preferred to multiple remove/add calls as this doesn't
+ * update the resource data structures after each modification.
+ *
+ * Set to null or an empty list to clear the set of loaders.
+ *
+ * The caller maintains responsibility for holding references to the added
+ * {@link ResourcesProvider}s and closing them after this method has been called.
+ *
+ * @param resourceLoadersAndProviders a list of pairs to add
+ */
+ public void setLoaders(
+ @Nullable List<Pair<ResourceLoader, ResourcesProvider>> resourceLoadersAndProviders) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ if (ArrayUtils.isEmpty(resourceLoadersAndProviders)) {
+ return;
+ }
+
+ ResourcesManager.getInstance().registerForLoaders(this);
+ mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ }
+
+ mResourceLoaderManager.setLoaders(resourceLoadersAndProviders);
+ }
+ }
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index b72544c..84489cf 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -57,6 +57,8 @@
import com.android.internal.util.GrowingArrayUtils;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -376,7 +378,7 @@
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
try {
synchronized (mAccessLock) {
- if (false) {
+ if (DEBUG_CONFIG) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is "
+ mDisplayAdjustments.getCompatibilityInfo());
@@ -572,6 +574,20 @@
}
}
+ /**
+ * Wipe all caches that might be read and return an outdated object when resolving a resource.
+ */
+ public void clearAllCaches() {
+ synchronized (mAccessLock) {
+ mDrawableCache.clear();
+ mColorDrawableCache.clear();
+ mComplexColorCache.clear();
+ mAnimatorCache.clear();
+ mStateListAnimatorCache.clear();
+ flushLayoutCache();
+ }
+ }
+
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
@@ -802,6 +818,27 @@
}
/**
+ * Loads a Drawable from an encoded image stream, or null.
+ *
+ * This call will handle closing the {@link InputStream}.
+ */
+ @Nullable
+ private Drawable decodeImageDrawable(@NonNull InputStream inputStream,
+ @NonNull Resources wrapper, @NonNull TypedValue value) {
+ ImageDecoder.Source src = ImageDecoder.createSource(wrapper, inputStream, value.density);
+ try {
+ return ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
+ } catch (IOException ignored) {
+ // This is okay. This may be something that ImageDecoder does not
+ // support, like SVG.
+ return null;
+ } finally {
+ IoUtils.closeQuietly(inputStream);
+ }
+ }
+
+ /**
* Loads a drawable from XML or resources stream.
*
* @return Drawable, or null if Drawable cannot be decoded.
@@ -865,8 +902,12 @@
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- AssetInputStream ais = (AssetInputStream) is;
- dr = decodeImageDrawable(ais, wrapper, value);
+ if (is instanceof AssetInputStream) {
+ AssetInputStream ais = (AssetInputStream) is;
+ dr = decodeImageDrawable(ais, wrapper, value);
+ } else {
+ dr = decodeImageDrawable(is, wrapper, value);
+ }
}
} finally {
stack.pop();
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 2ae1932..d43bd36 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -47,6 +47,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.io.Closeable;
import java.util.Arrays;
/**
@@ -54,7 +55,7 @@
*
* {@hide}
*/
-final class StringBlock {
+public final class StringBlock implements Closeable {
private static final String TAG = "AssetManager";
private static final boolean localLOGV = false;
@@ -175,6 +176,7 @@
}
}
+ @Override
public void close() {
synchronized (this) {
if (mOpen) {
@@ -517,7 +519,7 @@
* of this newly creating StringBlock.
*/
@UnsupportedAppUsage
- StringBlock(long obj, boolean useSparse) {
+ public StringBlock(long obj, boolean useSparse) {
mNative = obj;
mUseSparse = useSparse;
mOwnsNative = false;
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index 06cafdb..968ab40 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -22,8 +22,8 @@
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources.Theme;
import android.content.res.Resources.ThemeKey;
-import android.util.LongSparseArray;
import android.util.ArrayMap;
+import android.util.LongSparseArray;
import java.lang.ref.WeakReference;
@@ -234,4 +234,18 @@
return entry == null || (configChanges != 0
&& shouldInvalidateEntry(entry, configChanges));
}
+
+ public synchronized void clear() {
+ if (mThemedEntries != null) {
+ mThemedEntries.clear();
+ }
+
+ if (mUnthemedEntries != null) {
+ mUnthemedEntries.clear();
+ }
+
+ if (mNullThemedEntries != null) {
+ mNullThemedEntries.clear();
+ }
+ }
}
diff --git a/core/java/android/content/res/loader/DirectoryResourceLoader.java b/core/java/android/content/res/loader/DirectoryResourceLoader.java
new file mode 100644
index 0000000..7d90e72
--- /dev/null
+++ b/core/java/android/content/res/loader/DirectoryResourceLoader.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link ResourceLoader} that searches a directory for assets.
+ *
+ * Assumes that resource paths are resolvable child paths of the directory passed in.
+ */
+public class DirectoryResourceLoader implements ResourceLoader {
+
+ @NonNull
+ private final File mDirectory;
+
+ public DirectoryResourceLoader(@NonNull File directory) {
+ this.mDirectory = directory;
+ }
+
+ @Nullable
+ @Override
+ public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+ File file = findFile(path);
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ return new FileInputStream(file);
+ }
+
+ @Nullable
+ @Override
+ public ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+ File file = findFile(path);
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ /**
+ * Find the file for the given path encoded into the resource table.
+ */
+ @Nullable
+ public File findFile(@NonNull String path) {
+ return mDirectory.toPath().resolve(path).toFile();
+ }
+
+ @NonNull
+ public File getDirectory() {
+ return mDirectory;
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoader.java b/core/java/android/content/res/loader/ResourceLoader.java
new file mode 100644
index 0000000..af32aa2
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoader.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Exposes methods for overriding file-based resource loading from a {@link Resources}.
+ *
+ * To be used with {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)} and related
+ * methods to override resource loading.
+ *
+ * Note that this class doesn't actually contain any resource data. Non-file-based resources are
+ * loaded directly from the {@link ResourcesProvider}'s .arsc representation.
+ *
+ * An instance's methods will only be called if its corresponding {@link ResourcesProvider}'s
+ * resources table contains an entry for the resource ID being resolved,
+ * with the exception of the non-cookie variants of {@link AssetManager}'s openAsset and
+ * openNonAsset.
+ *
+ * Those methods search backwards through all {@link ResourceLoader}s and then any paths provided
+ * by the application or system.
+ *
+ * Otherwise, an ARSC that defines R.drawable.some_id must be provided if a {@link ResourceLoader}
+ * wants to point R.drawable.some_id to a different file on disk.
+ */
+public interface ResourceLoader {
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return a
+ * {@link Drawable} which should be returned by the parent
+ * {@link Resources#getDrawable(int, Resources.Theme)}.
+ *
+ * @param value the resolved {@link TypedValue} before it has been converted to a Drawable
+ * object
+ * @param id the R.drawable ID this resolution is for
+ * @param density the requested density
+ * @param theme the {@link Resources.Theme} resolved under
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+ * including calling through to {@link #loadAsset(String, int)} or {@link #loadAssetFd(String)}
+ */
+ @Nullable
+ default Drawable loadDrawable(@NonNull TypedValue value, int id, int density,
+ @Nullable Resources.Theme theme) {
+ return null;
+ }
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+ * {@link XmlResourceParser} which should be returned by the parent
+ * {@link Resources#getDrawable(int, Resources.Theme)}.
+ *
+ * @param path the string that was found in the string pool
+ * @param id the XML ID this resolution is for, can be R.anim, R.layout, or R.xml
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+ * including calling through to {@link #loadAssetFd(String)} (String, int)}
+ */
+ @Nullable
+ default XmlResourceParser loadXmlResourceParser(@NonNull String path, @AnyRes int id) {
+ return null;
+ }
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+ * {@link InputStream} which should be returned when an asset is loaded by {@link AssetManager}.
+ * Assets will be loaded from a provider's root, with anything in its assets subpath prefixed
+ * with "assets/".
+ *
+ * @param path the asset path to load
+ * @param accessMode {@link AssetManager} access mode; does not have to be respected
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+ */
+ @Nullable
+ default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+ return null;
+ }
+
+ /**
+ * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
+ *
+ * @param path the asset path to load
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+ */
+ @Nullable
+ default ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+ return null;
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoaderManager.java b/core/java/android/content/res/loader/ResourceLoaderManager.java
new file mode 100644
index 0000000..ddbfa81
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoaderManager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.Nullable;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.ResourcesImpl;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ResourceLoaderManager {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final List<Pair<ResourceLoader, ResourcesProvider>> mResourceLoaders =
+ new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private ResourcesImpl mResourcesImpl;
+
+ public ResourceLoaderManager(ResourcesImpl resourcesImpl) {
+ this.mResourcesImpl = resourcesImpl;
+ this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
+ }
+
+ /**
+ * Copies the list to ensure that ongoing mutations don't affect the list if it's being used
+ * as a search set.
+ *
+ * @see Resources#getLoaders()
+ */
+ public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+ synchronized (mLock) {
+ return new ArrayList<>(mResourceLoaders);
+ }
+ }
+
+ /**
+ * Returns a list for searching for a loader. Locks and copies the list to ensure that
+ * ongoing mutations don't affect the search set.
+ */
+ public List<Pair<ResourceLoader, ResourcesProvider>> getInternalList() {
+ synchronized (mLock) {
+ return new ArrayList<>(mResourceLoaders);
+ }
+ }
+
+ /**
+ * TODO(b/136251855): Consider optional boolean ignoreConfigurations to allow ResourceLoader
+ * to override every configuration in the target package
+ *
+ * @see Resources#addLoader(ResourceLoader, ResourcesProvider)
+ */
+ public void addLoader(ResourceLoader resourceLoader, ResourcesProvider resourcesProvider,
+ int index) {
+ synchronized (mLock) {
+ for (int listIndex = 0; listIndex < mResourceLoaders.size(); listIndex++) {
+ if (Objects.equals(mResourceLoaders.get(listIndex).first, resourceLoader)) {
+ throw new IllegalArgumentException("Cannot add the same ResourceLoader twice");
+ }
+ }
+
+ mResourceLoaders.add(index, Pair.create(resourceLoader, resourcesProvider));
+ updateLoaders();
+ }
+ }
+
+ /**
+ * @see Resources#removeLoader(ResourceLoader)
+ */
+ public int removeLoader(ResourceLoader resourceLoader) {
+ synchronized (mLock) {
+ int indexOfLoader = -1;
+
+ for (int index = 0; index < mResourceLoaders.size(); index++) {
+ if (mResourceLoaders.get(index).first == resourceLoader) {
+ indexOfLoader = index;
+ break;
+ }
+ }
+
+ if (indexOfLoader < 0) {
+ return indexOfLoader;
+ }
+
+ mResourceLoaders.remove(indexOfLoader);
+ updateLoaders();
+ return indexOfLoader;
+ }
+ }
+
+ /**
+ * @see Resources#setLoaders(List)
+ */
+ public void setLoaders(
+ @Nullable List<Pair<ResourceLoader, ResourcesProvider>> newLoadersAndProviders) {
+ synchronized (mLock) {
+ if (ArrayUtils.isEmpty(newLoadersAndProviders)) {
+ mResourceLoaders.clear();
+ updateLoaders();
+ return;
+ }
+
+ int size = newLoadersAndProviders.size();
+ for (int newIndex = 0; newIndex < size; newIndex++) {
+ ResourceLoader resourceLoader = newLoadersAndProviders.get(newIndex).first;
+ for (int oldIndex = 0; oldIndex < mResourceLoaders.size(); oldIndex++) {
+ if (Objects.equals(mResourceLoaders.get(oldIndex).first, resourceLoader)) {
+ throw new IllegalArgumentException(
+ "Cannot add the same ResourceLoader twice");
+ }
+ }
+ }
+
+ mResourceLoaders.clear();
+ mResourceLoaders.addAll(newLoadersAndProviders);
+
+ updateLoaders();
+ }
+ }
+
+ /**
+ * Swap the tracked {@link ResourcesImpl} and reattach any loaders to it.
+ */
+ public void onImplUpdate(ResourcesImpl resourcesImpl) {
+ synchronized (mLock) {
+ this.mResourcesImpl = resourcesImpl;
+ updateLoaders();
+ }
+ }
+
+ private void updateLoaders() {
+ synchronized (mLock) {
+ AssetManager assetManager = mResourcesImpl.getAssets();
+ ApkAssets[] existingApkAssets = assetManager.getApkAssets();
+ int baseApkAssetsSize = 0;
+ for (int index = existingApkAssets.length - 1; index >= 0; index--) {
+ // Loaders are always last, so the first non-loader is the end of the base assets
+ if (!existingApkAssets[index].isForLoader()) {
+ baseApkAssetsSize = index + 1;
+ break;
+ }
+ }
+
+ List<ApkAssets> newAssets = new ArrayList<>();
+ for (int index = 0; index < baseApkAssetsSize; index++) {
+ newAssets.add(existingApkAssets[index]);
+ }
+
+ int size = mResourceLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ApkAssets apkAssets = mResourceLoaders.get(index).second.getApkAssets();
+ newAssets.add(apkAssets);
+ }
+
+ assetManager.setApkAssets(newAssets.toArray(new ApkAssets[0]), true);
+
+ // Short of resolving every resource, it's too difficult to determine what has changed
+ // when a resource loader is changed, so just clear everything.
+ mResourcesImpl.clearAllCaches();
+ }
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
new file mode 100644
index 0000000..050aeb7
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.ApkAssets;
+import android.content.res.Resources;
+import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Provides methods to load resources from an .apk or .arsc file to pass to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}.
+ *
+ * It is the responsibility of the app to close any instances.
+ */
+public final class ResourcesProvider implements AutoCloseable, Closeable {
+
+ /**
+ * Contains no data, assuming that any resource loading behavior will be handled in the
+ * corresponding {@link ResourceLoader}.
+ */
+ @NonNull
+ public static ResourcesProvider empty() {
+ return new ResourcesProvider(ApkAssets.loadEmptyForLoader());
+ }
+
+ /**
+ * Read from an .apk file descriptor.
+ *
+ * The file descriptor is duplicated and the one passed in may be closed by the application
+ * at any time.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .apk file representation in memory.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .arsc file descriptor.
+ *
+ * The file descriptor is duplicated and the one passed in may be closed by the application
+ * at any time.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromArsc(@NonNull ParcelFileDescriptor fileDescriptor)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .arsc file representation in memory.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromArsc(@NonNull SharedMemory sharedMemory)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()));
+ }
+
+ /**
+ * Read from a split installed alongside the application, which may not have been
+ * loaded initially because the application requested isolated split loading.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromSplit(@NonNull Context context,
+ @NonNull String splitName) throws IOException {
+ ApplicationInfo appInfo = context.getApplicationInfo();
+ int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
+ if (splitIndex < 0) {
+ throw new IllegalArgumentException("Split " + splitName + " not found");
+ }
+
+ String splitPath = appInfo.getSplitCodePaths()[splitIndex];
+ return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath));
+ }
+
+
+ @NonNull
+ private final ApkAssets mApkAssets;
+
+ private ResourcesProvider(@NonNull ApkAssets apkAssets) {
+ this.mApkAssets = apkAssets;
+ }
+
+ /** @hide */
+ @NonNull
+ public ApkAssets getApkAssets() {
+ return mApkAssets;
+ }
+
+ @Override
+ public void close() {
+ try {
+ mApkAssets.close();
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+}
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index 106b7be..fe9141c 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -15,6 +15,7 @@
*/
package android.net;
+import android.telephony.SubscriptionPlan;
/** {@hide} */
oneway interface INetworkPolicyListener {
@@ -22,5 +23,6 @@
void onMeteredIfacesChanged(in String[] meteredIfaces);
void onRestrictBackgroundChanged(boolean restrictBackground);
void onUidPoliciesChanged(int uid, int uidPolicies);
- void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask);
+ void onSubscriptionOverride(int subId, int overrideMask, int overrideValue);
+ void onSubscriptionPlansChanged(int subId, in SubscriptionPlan[] plans);
}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 90327663..385cb1d 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -76,7 +76,7 @@
SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
String getSubscriptionPlansOwner(int subId);
- void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask, long timeoutMillis, String callingPackage);
+ void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage);
void factoryReset(String subscriber);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 628dcd2..9150aae 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -31,6 +31,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.telephony.SubscriptionPlan;
import android.util.DebugUtils;
import android.util.Pair;
import android.util.Range;
@@ -380,7 +381,8 @@
@Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
@Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
@Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
- @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
- long networkTypeMask) { }
+ @Override public void onSubscriptionOverride(int subId, int overrideMask,
+ int overrideValue) { }
+ @Override public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { }
}
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 2c9333b..ef3afab 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -1085,4 +1085,13 @@
StrictMode.clearGatheredViolations();
return res;
}
+
+ /**
+ * Returns the specified service from servicemanager. If the service is not running,
+ * servicemanager will attempt to start it, and this function will wait for it to be ready.
+ * Returns nullptr only if there are permission problems or fatal errors.
+ * @hide
+ */
+ public static final native @Nullable IBinder waitForService(@NonNull String serviceName)
+ throws RemoteException;
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index bcb94ce..fdb44e7 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -35,12 +35,15 @@
import android.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
import android.util.Log;
+import android.util.Size;
import dalvik.system.CloseGuard;
import dalvik.system.VMRuntime;
@@ -204,6 +207,10 @@
/**
* Create a new ParcelFileDescriptor accessing a given file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
*
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
@@ -226,6 +233,10 @@
/**
* Create a new ParcelFileDescriptor accessing a given file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
*
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 654b0f7..26c1ec1 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -31,8 +31,8 @@
void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
String callerPackageName, in AndroidFuture callback);
void getRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
- void restoreRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
- void restoreDelayedRuntimePermissionBackup(String packageName, in UserHandle user,
+ void stageAndApplyRuntimePermissionsBackup(in UserHandle user, in ParcelFileDescriptor pipe);
+ void applyStagedRuntimePermissionBackup(String packageName, in UserHandle user,
in AndroidFuture callback);
void getAppPermissions(String packageName, in AndroidFuture callback);
void revokeRuntimePermission(String packageName, String permissionName);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 923d9f8..421e29e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -62,6 +62,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -139,20 +140,6 @@
}
/**
- * Callback for delivering the result of {@link #getRuntimePermissionBackup}.
- *
- * @hide
- */
- public interface OnGetRuntimePermissionBackupCallback {
- /**
- * The result for {@link #getRuntimePermissionBackup}.
- *
- * @param backup The backup file
- */
- void onGetRuntimePermissionsBackup(@NonNull byte[] backup);
- }
-
- /**
* Callback for delivering the result of {@link #getAppPermissions}.
*
* @hide
@@ -246,6 +233,24 @@
}
/**
+ * Throw a {@link SecurityException} if not at least one of the permissions is granted.
+ *
+ * @param requiredPermissions A list of permissions. Any of of them if sufficient to pass the
+ * check
+ */
+ private void enforceSomePermissionsGrantedToSelf(@NonNull String... requiredPermissions) {
+ for (String requiredPermission : requiredPermissions) {
+ if (mContext.checkSelfPermission(requiredPermission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ throw new SecurityException("At lest one of the following permissions is required: "
+ + Arrays.toString(requiredPermissions));
+ }
+
+ /**
* Revoke a set of runtime permissions for various apps.
*
* @param request The permissions to revoke as {@code Map<packageName, List<permission>>}
@@ -268,11 +273,7 @@
}
// Check required permission to fail immediately instead of inside the oneway binder call
- if (mContext.checkSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
- + " required");
- }
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
mRemoteService.postAsync(service -> {
Bundle bundledizedRequest = new Bundle();
@@ -358,46 +359,61 @@
*
* @param user The user to be backed up
* @param executor Executor on which to invoke the callback
- * @param callback Callback to receive the result
- *
- * @hide
+ * @param callback Callback to receive the result. The resulting backup-file is opaque and no
+ * guarantees are made other than that the file can be send to
+ * {@link #restoreRuntimePermissionBackup} in this and future versions of
+ * Android.
*/
@RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
public void getRuntimePermissionBackup(@NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnGetRuntimePermissionBackupCallback callback) {
+ @NonNull Consumer<byte[]> callback) {
checkNotNull(user);
checkNotNull(executor);
checkNotNull(callback);
+ // Check required permission to fail immediately instead of inside the oneway binder call
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.GET_RUNTIME_PERMISSIONS);
+
mRemoteService.postAsync(service -> RemoteStream.receiveBytes(remotePipe -> {
service.getRuntimePermissionBackup(user, remotePipe);
})).whenCompleteAsync((bytes, err) -> {
if (err != null) {
Log.e(TAG, "Error getting permission backup", err);
- callback.onGetRuntimePermissionsBackup(EmptyArray.BYTE);
+ callback.accept(EmptyArray.BYTE);
} else {
- callback.onGetRuntimePermissionsBackup(bytes);
+ callback.accept(bytes);
}
}, executor);
}
/**
- * Restore a backup of the runtime permissions.
+ * Restore a {@link #getRuntimePermissionBackup backup-file} of the runtime permissions.
*
- * @param backup the backup to restore. The backup is sent asynchronously, hence it should not
- * be modified after calling this method.
+ * <p>This might leave some part of the backup-file unapplied if an package mentioned in the
+ * backup-file is not yet installed. It is required that
+ * {@link #applyStagedRuntimePermissionBackup} is called after any package is installed to
+ * apply the rest of the backup-file.
+ *
+ * @param backup the backup-file to restore. The backup is sent asynchronously, hence it should
+ * not be modified after calling this method.
* @param user The user to be restore
- *
- * @hide
*/
- @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
- public void restoreRuntimePermissionBackup(@NonNull byte[] backup, @NonNull UserHandle user) {
+ @RequiresPermission(anyOf = {
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
+ })
+ public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[] backup,
+ @NonNull UserHandle user) {
checkNotNull(backup);
checkNotNull(user);
+ // Check required permission to fail immediately instead of inside the oneway binder call
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
+
mRemoteService.postAsync(service -> RemoteStream.sendBytes(remotePipe -> {
- service.restoreRuntimePermissionBackup(user, remotePipe);
+ service.stageAndApplyRuntimePermissionsBackup(user, remotePipe);
}, backup))
.whenComplete((nullResult, err) -> {
if (err != null) {
@@ -407,17 +423,22 @@
}
/**
- * Restore a backup of the runtime permissions that has been delayed.
+ * Restore unapplied parts of a {@link #stageAndApplyRuntimePermissionsBackup previously staged}
+ * backup-file of the runtime permissions.
+ *
+ * <p>This should be called every time after a package is installed until the callback
+ * reports that there is no more unapplied backup left.
*
* @param packageName The package that is ready to have it's permissions restored.
- * @param user The user to restore
+ * @param user The user the package belongs to
* @param executor Executor to execute the callback on
- * @param callback Is called with {@code true} iff there is still more delayed backup left
- *
- * @hide
+ * @param callback Is called with {@code true} iff there is still more unapplied backup left
*/
- @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
- public void restoreDelayedRuntimePermissionBackup(@NonNull String packageName,
+ @RequiresPermission(anyOf = {
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
+ })
+ public void applyStagedRuntimePermissionBackup(@NonNull String packageName,
@NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
@@ -426,13 +447,17 @@
checkNotNull(executor);
checkNotNull(callback);
+ // Check required permission to fail immediately instead of inside the oneway binder call
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
+
mRemoteService.postAsync(service -> {
- AndroidFuture<Boolean> restoreDelayedRuntimePermissionBackupResult =
+ AndroidFuture<Boolean> applyStagedRuntimePermissionBackupResult =
new AndroidFuture<>();
- service.restoreDelayedRuntimePermissionBackup(packageName, user,
- restoreDelayedRuntimePermissionBackupResult);
- return restoreDelayedRuntimePermissionBackupResult;
- }).whenCompleteAsync((restoreDelayedRuntimePermissionBackupResult, err) -> {
+ service.applyStagedRuntimePermissionBackup(packageName, user,
+ applyStagedRuntimePermissionBackupResult);
+ return applyStagedRuntimePermissionBackupResult;
+ }).whenCompleteAsync((applyStagedRuntimePermissionBackupResult, err) -> {
long token = Binder.clearCallingIdentity();
try {
if (err != null) {
@@ -440,7 +465,7 @@
callback.accept(true);
} else {
callback.accept(
- Boolean.TRUE.equals(restoreDelayedRuntimePermissionBackupResult));
+ Boolean.TRUE.equals(applyStagedRuntimePermissionBackupResult));
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 7363d77..8f765fa 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -54,6 +54,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -105,31 +106,54 @@
public abstract void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
@NonNull OutputStream backup, @NonNull Runnable callback);
+
+ /**
+ * @deprecated Implement {@link #onStageAndApplyRuntimePermissionsBackup} instead
+ */
+ @Deprecated
+ @BinderThread
+ public void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
+ @NonNull InputStream backup, @NonNull Runnable callback) {
+ }
+
/**
* Restore a backup of the runtime permissions.
*
* <p>If an app mentioned in the backup is not installed the state should be saved to later
- * be restored via {@link #onRestoreDelayedRuntimePermissionsBackup}.
+ * be restored via {@link #onApplyStagedRuntimePermissionBackup}.
*
* @param user The user to restore
* @param backup The stream to read the backup from
* @param callback Callback waiting for operation to be complete
*/
@BinderThread
- public abstract void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
- @NonNull InputStream backup, @NonNull Runnable callback);
+ public void onStageAndApplyRuntimePermissionsBackup(@NonNull UserHandle user,
+ @NonNull InputStream backup, @NonNull Runnable callback) {
+ onRestoreRuntimePermissionsBackup(user, backup, callback);
+ }
+
+ /**
+ * @deprecated Implement {@link #onApplyStagedRuntimePermissionBackup} instead
+ */
+ @Deprecated
+ @BinderThread
+ public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
+ @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
+ }
/**
* Restore the permission state of an app that was provided in
- * {@link #onRestoreRuntimePermissionsBackup} but could not be restored back then.
+ * {@link #onStageAndApplyRuntimePermissionsBackup} but could not be restored back then.
*
* @param packageName The app to restore
* @param user The user to restore
* @param callback Callback waiting for whether there is still delayed backup left
*/
@BinderThread
- public abstract void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
- @NonNull UserHandle user, @NonNull Consumer<Boolean> callback);
+ public void onApplyStagedRuntimePermissionBackup(@NonNull String packageName,
+ @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
+ onRestoreDelayedRuntimePermissionsBackup(packageName, user, callback);
+ }
/**
* Gets the runtime permissions for an app.
@@ -238,7 +262,8 @@
request.put(packageName, permissions);
}
- enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
// Verify callerPackageName
try {
@@ -258,12 +283,33 @@
});
}
+ /**
+ * Throw a {@link SecurityException} if not at least one of the permissions is granted.
+ *
+ * @param requiredPermissions A list of permissions. Any of of them if sufficient to
+ * pass the check
+ */
+ private void enforceSomePermissionsGrantedToCaller(
+ @NonNull String... requiredPermissions) {
+ for (String requiredPermission : requiredPermissions) {
+ if (checkCallingPermission(requiredPermission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ throw new SecurityException(
+ "At lest one of the following permissions is required: " + Arrays.toString(
+ requiredPermissions));
+ }
+
+
@Override
public void getRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) {
checkNotNull(user);
checkNotNull(pipe);
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
try (OutputStream backup = new ParcelFileDescriptor.AutoCloseOutputStream(pipe)) {
CountDownLatch latch = new CountDownLatch(1);
@@ -277,15 +323,17 @@
}
@Override
- public void restoreRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) {
+ public void stageAndApplyRuntimePermissionsBackup(UserHandle user,
+ ParcelFileDescriptor pipe) {
checkNotNull(user);
checkNotNull(pipe);
- enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
try (InputStream backup = new ParcelFileDescriptor.AutoCloseInputStream(pipe)) {
CountDownLatch latch = new CountDownLatch(1);
- onRestoreRuntimePermissionsBackup(user, backup, latch::countDown);
+ onStageAndApplyRuntimePermissionsBackup(user, backup, latch::countDown);
latch.await();
} catch (IOException e) {
Log.e(LOG_TAG, "Could not open pipe to read backup from", e);
@@ -295,15 +343,16 @@
}
@Override
- public void restoreDelayedRuntimePermissionBackup(String packageName, UserHandle user,
+ public void applyStagedRuntimePermissionBackup(String packageName, UserHandle user,
AndroidFuture callback) {
checkNotNull(packageName);
checkNotNull(user);
checkNotNull(callback);
- enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
- onRestoreDelayedRuntimePermissionsBackup(packageName, user, callback::complete);
+ onApplyStagedRuntimePermissionBackup(packageName, user, callback::complete);
}
@Override
@@ -311,7 +360,7 @@
checkNotNull(packageName, "packageName");
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
onGetAppPermissions(packageName, callback::complete);
}
@@ -321,7 +370,8 @@
checkNotNull(packageName, "packageName");
checkNotNull(permissionName, "permissionName");
- enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
CountDownLatch latch = new CountDownLatch(1);
PermissionControllerService.this.onRevokeRuntimePermission(packageName,
@@ -340,7 +390,7 @@
checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED);
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
onCountPermissionApps(permissionNames, flags, callback::complete);
}
@@ -351,7 +401,7 @@
checkArgumentNonnegative(numMillis);
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
onGetPermissionUsages(countSystem, numMillis, callback::complete);
}
@@ -369,15 +419,17 @@
checkNotNull(callback);
if (grantState == PERMISSION_GRANT_STATE_DENIED) {
- enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
}
if (grantState == PERMISSION_GRANT_STATE_DENIED) {
- enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
}
- enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
- null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
packageName, permission, grantState, callback::complete);
@@ -387,8 +439,8 @@
public void grantOrUpgradeDefaultRuntimePermissions(@NonNull AndroidFuture callback) {
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
- null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
onGrantOrUpgradeDefaultRuntimePermissions(() -> callback.complete(true));
}
diff --git a/core/java/android/print/TEST_MAPPING b/core/java/android/print/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/core/java/android/print/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPrintTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
+ }
+ ]
+}
diff --git a/core/java/android/print/pdf/TEST_MAPPING b/core/java/android/print/pdf/TEST_MAPPING
new file mode 100644
index 0000000..d763598
--- /dev/null
+++ b/core/java/android/print/pdf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPdfTestCases"
+ }
+ ]
+}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index fd81178..eb09930 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -363,15 +363,22 @@
* <p>
* Type: INTEGER (int)
*
- * @see #FLAG_SUPPORTS_WRITE
- * @see #FLAG_SUPPORTS_DELETE
- * @see #FLAG_SUPPORTS_THUMBNAIL
+ * @see #FLAG_DIR_BLOCKS_TREE
* @see #FLAG_DIR_PREFERS_GRID
* @see #FLAG_DIR_PREFERS_LAST_MODIFIED
- * @see #FLAG_VIRTUAL_DOCUMENT
+ * @see #FLAG_DIR_SUPPORTS_CREATE
+ * @see #FLAG_PARTIAL
* @see #FLAG_SUPPORTS_COPY
+ * @see #FLAG_SUPPORTS_DELETE
+ * @see #FLAG_SUPPORTS_METADATA
* @see #FLAG_SUPPORTS_MOVE
* @see #FLAG_SUPPORTS_REMOVE
+ * @see #FLAG_SUPPORTS_RENAME
+ * @see #FLAG_SUPPORTS_SETTINGS
+ * @see #FLAG_SUPPORTS_THUMBNAIL
+ * @see #FLAG_SUPPORTS_WRITE
+ * @see #FLAG_VIRTUAL_DOCUMENT
+ * @see #FLAG_WEB_LINKABLE
*/
public static final String COLUMN_FLAGS = "flags";
@@ -542,6 +549,23 @@
* @see DocumentsContract#getDocumentMetadata(ContentInterface, Uri)
*/
public static final int FLAG_SUPPORTS_METADATA = 1 << 14;
+
+ /**
+ * Flag indicating that a document is a directory that wants to block itself
+ * from being selected when the user launches an {@link Intent#ACTION_OPEN_DOCUMENT_TREE}
+ * intent. Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+ * <p>
+ * Note that this flag <em>only</em> applies to the single directory to which it is
+ * applied. It does <em>not</em> block the user from selecting either a parent or
+ * child directory during an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request.
+ * In particular, the only way to guarantee that a specific directory can never
+ * be granted via an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request is to ensure
+ * that both it and <em>all of its parent directories</em> have set this flag.
+ *
+ * @see Intent#ACTION_OPEN_DOCUMENT_TREE
+ * @see #COLUMN_FLAGS
+ */
+ public static final int FLAG_DIR_BLOCKS_TREE = 1 << 15;
}
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 079a42d..a1333df 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -39,7 +39,6 @@
import android.content.Intent;
import android.content.UriPermission;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
@@ -47,6 +46,7 @@
import android.graphics.PostProcessor;
import android.media.ExifInterface;
import android.media.MediaFile;
+import android.media.MediaFormat;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -69,15 +69,19 @@
import com.android.internal.annotations.GuardedBy;
+import libcore.util.HexEncoding;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
@@ -2058,7 +2062,17 @@
/**
* A non human readable key calculated from the TITLE, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String TITLE_KEY = "title_key";
@@ -2103,7 +2117,17 @@
/**
* A non human readable key calculated from the ARTIST, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ARTIST_KEY = "artist_key";
@@ -2128,7 +2152,17 @@
/**
* A non human readable key calculated from the ALBUM, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ALBUM_KEY = "album_key";
@@ -2185,91 +2219,89 @@
public static final String IS_AUDIOBOOK = "is_audiobook";
/**
- * The genre of the audio file, if any
- * Does not exist in the database - only used by the media scanner for inserts.
- * @hide
+ * The id of the genre the audio file is from, if any
*/
- @Deprecated
- // @Column(Cursor.FIELD_TYPE_STRING)
+ @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
+ public static final String GENRE_ID = "genre_id";
+
+ /**
+ * The genre of the audio file, if any.
+ */
+ @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String GENRE = "genre";
/**
- * The resource URI of a localized title, if any
+ * A non human readable key calculated from the GENRE, used for
+ * searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
+ */
+ @Deprecated
+ @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
+ public static final String GENRE_KEY = "genre_key";
+
+ /**
+ * The resource URI of a localized title, if any.
+ * <p>
* Conforms to this pattern:
- * Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
- * Authority: Package Name of ringtone title provider
- * First Path Segment: Type of resource (must be "string")
- * Second Path Segment: Resource ID of title
- * @hide
+ * <ul>
+ * <li>Scheme: {@link ContentResolver#SCHEME_ANDROID_RESOURCE}
+ * <li>Authority: Package Name of ringtone title provider
+ * <li>First Path Segment: Type of resource (must be "string")
+ * <li>Second Path Segment: Resource ID of title
+ * </ul>
*/
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String TITLE_RESOURCE_URI = "title_resource_uri";
}
+ private static final Pattern PATTERN_TRIM_BEFORE = Pattern.compile(
+ "(?i)(^(the|an|a) |,\\s*(the|an|a)$|[^\\w\\s]|^\\s+|\\s+$)");
+ private static final Pattern PATTERN_TRIM_AFTER = Pattern.compile(
+ "(^(00)+|(00)+$)");
+
/**
- * Converts a name to a "key" that can be used for grouping, sorting
- * and searching.
- * The rules that govern this conversion are:
- * - remove 'special' characters like ()[]'!?.,
- * - remove leading/trailing spaces
- * - convert everything to lowercase
- * - remove leading "the ", "an " and "a "
- * - remove trailing ", the|an|a"
- * - remove accents. This step leaves us with CollationKey data,
- * which is not human readable
+ * Converts a user-visible string into a "key" that can be used for
+ * grouping, sorting, and searching.
*
- * @param name The artist or album name to convert
- * @return The "key" for the given name.
+ * @return Opaque token that should not be parsed or displayed to users.
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
- public static String keyFor(String name) {
- if (name != null) {
- boolean sortfirst = false;
- if (name.equals(UNKNOWN_STRING)) {
- return "\001";
- }
- // Check if the first character is \001. We use this to
- // force sorting of certain special files, like the silent ringtone.
- if (name.startsWith("\001")) {
- sortfirst = true;
- }
- name = name.trim().toLowerCase();
- if (name.startsWith("the ")) {
- name = name.substring(4);
- }
- if (name.startsWith("an ")) {
- name = name.substring(3);
- }
- if (name.startsWith("a ")) {
- name = name.substring(2);
- }
- if (name.endsWith(", the") || name.endsWith(",the") ||
- name.endsWith(", an") || name.endsWith(",an") ||
- name.endsWith(", a") || name.endsWith(",a")) {
- name = name.substring(0, name.lastIndexOf(','));
- }
- name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
- if (name.length() > 0) {
- // Insert a separator between the characters to avoid
- // matches on a partial character. If we ever change
- // to start-of-word-only matches, this can be removed.
- StringBuilder b = new StringBuilder();
- b.append('.');
- int nl = name.length();
- for (int i = 0; i < nl; i++) {
- b.append(name.charAt(i));
- b.append('.');
- }
- name = b.toString();
- String key = DatabaseUtils.getCollationKey(name);
- if (sortfirst) {
- key = "\001" + key;
- }
- return key;
- } else {
- return "";
- }
+ @Deprecated
+ public static @Nullable String keyFor(@Nullable String name) {
+ if (TextUtils.isEmpty(name)) return null;
+
+ if (UNKNOWN_STRING.equals(name)) {
+ return "01";
}
- return null;
+
+ final boolean sortFirst = name.startsWith("\001");
+
+ name = PATTERN_TRIM_BEFORE.matcher(name).replaceAll("");
+ if (TextUtils.isEmpty(name)) return null;
+
+ final Collator c = Collator.getInstance(Locale.ROOT);
+ c.setStrength(Collator.PRIMARY);
+ name = HexEncoding.encodeToString(c.getCollationKey(name).toByteArray(), false);
+
+ name = PATTERN_TRIM_AFTER.matcher(name).replaceAll("");
+ if (sortFirst) {
+ name = "01" + name;
+ }
+ return name;
}
public static final class Media implements AudioColumns {
@@ -2631,7 +2663,17 @@
/**
* A non human readable key calculated from the ARTIST, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ARTIST_KEY = "artist_key";
@@ -2735,6 +2777,23 @@
public static final String ARTIST = "artist";
/**
+ * A non human readable key calculated from the ARTIST, used for
+ * searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
+ */
+ @Deprecated
+ @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
+ public static final String ARTIST_KEY = "artist_key";
+
+ /**
* The number of songs on this album
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
@@ -2769,7 +2828,17 @@
/**
* A non human readable key calculated from the ALBUM, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ALBUM_KEY = "album_key";
@@ -3007,22 +3076,32 @@
public static final String BOOKMARK = "bookmark";
/**
- * The standard of color aspects
- * @hide
+ * The color standard of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_STANDARD_BT709
+ * @see MediaFormat#COLOR_STANDARD_BT601_PAL
+ * @see MediaFormat#COLOR_STANDARD_BT601_NTSC
+ * @see MediaFormat#COLOR_STANDARD_BT2020
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_STANDARD = "color_standard";
/**
- * The transfer of color aspects
- * @hide
+ * The color transfer of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_TRANSFER_LINEAR
+ * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
+ * @see MediaFormat#COLOR_TRANSFER_ST2084
+ * @see MediaFormat#COLOR_TRANSFER_HLG
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_TRANSFER = "color_transfer";
/**
- * The range of color aspects
- * @hide
+ * The color range of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_RANGE_LIMITED
+ * @see MediaFormat#COLOR_RANGE_FULL
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_RANGE = "color_range";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 457dcc0..065ee0b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8241,20 +8241,6 @@
public static final String AWARE_LOCK_ENABLED = "aware_lock_enabled";
/**
- * The settings values which should only be restored if the target device is the
- * same as the source device
- *
- * NOTE: Settings are backed up and restored in the order they appear
- * in this array. If you have one setting depending on another,
- * make sure that they are ordered appropriately.
- *
- * @hide
- */
- public static final String[] DEVICE_SPECIFIC_SETTINGS_TO_BACKUP = {
- DISPLAY_DENSITY_FORCED,
- };
-
- /**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
*
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index f1c870d..dd2586c 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -126,11 +126,29 @@
= "android.service.quicksettings.ACTIVE_TILE";
/**
+ * Meta-data for a tile to support {@code BooleanState}.
+ * <p>
+ * BooleanState is for tiles that should support switch tile behavior in accessibility. This is
+ * the behavior of most of the framework tiles.
+ *
+ * To make a TileService support BooleanState, set this meta-data to true on the TileService's
+ * manifest declaration.
+ * <pre class="prettyprint">
+ * {@literal
+ * <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE"
+ * android:value="true" />
+ * }
+ * </pre>
+ */
+ public static final String META_DATA_BOOLEAN_TILE =
+ "android.service.quicksettings.BOOLEAN_TILE";
+
+ /**
* Used to notify SysUI that Listening has be requested.
* @hide
*/
- public static final String ACTION_REQUEST_LISTENING
- = "android.service.quicksettings.action.REQUEST_LISTENING";
+ public static final String ACTION_REQUEST_LISTENING =
+ "android.service.quicksettings.action.REQUEST_LISTENING";
/**
* @hide
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 20e0d14..bc5edf8 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -255,7 +255,7 @@
if (value == 0 && flagsWasZero) {
return constNameWithoutPrefix(prefix, field);
}
- if ((flags & value) == value) {
+ if (value != 0 && (flags & value) == value) {
flags &= ~value;
res.append(constNameWithoutPrefix(prefix, field)).append('|');
}
diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/util/TimestampedValue.java
index 1289e4d..4505673 100644
--- a/core/java/android/util/TimestampedValue.java
+++ b/core/java/android/util/TimestampedValue.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.os.SystemClock;
import java.util.Objects;
@@ -30,14 +31,14 @@
* If a suitable clock is used the reference time can be used to identify the age of a value or
* ordering between values.
*
- * <p>To read and write a timestamped value from / to a Parcel see
- * {@link #readFromParcel(Parcel, ClassLoader, Class)} and
- * {@link #writeToParcel(Parcel, TimestampedValue)}.
+ * <p>This class implements {@link Parcelable} for convenience but instances will only actually be
+ * parcelable if the value type held is {@code null}, {@link Parcelable}, or one of the other types
+ * supported by {@link Parcel#writeValue(Object)} / {@link Parcel#readValue(ClassLoader)}.
*
* @param <T> the type of the value with an associated timestamp
* @hide
*/
-public final class TimestampedValue<T> {
+public final class TimestampedValue<T> implements Parcelable {
private final long mReferenceTimeMillis;
private final T mValue;
@@ -81,57 +82,43 @@
}
/**
- * Read a {@link TimestampedValue} from a parcel that was stored using
- * {@link #writeToParcel(Parcel, TimestampedValue)}.
- *
- * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
- * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
- * supported by those methods.
- *
- * @param in the Parcel to read from
- * @param classLoader the ClassLoader to pass to {@link Parcel#readValue(ClassLoader)}
- * @param valueClass the expected type of the value, typically the same as {@code <T>} but can
- * also be a subclass
- * @throws RuntimeException if the value read is not compatible with {@code valueClass} or the
- * object could not be read
- */
- @SuppressWarnings("unchecked")
- @NonNull
- public static <T> TimestampedValue<T> readFromParcel(
- @NonNull Parcel in, @Nullable ClassLoader classLoader, Class<? extends T> valueClass) {
- long referenceTimeMillis = in.readLong();
- T value = (T) in.readValue(classLoader);
- // Equivalent to static code: if (!(value.getClass() instanceof {valueClass})) {
- if (value != null && !valueClass.isAssignableFrom(value.getClass())) {
- throw new RuntimeException("Value was of type " + value.getClass()
- + " is not assignable to " + valueClass);
- }
- return new TimestampedValue<>(referenceTimeMillis, value);
- }
-
- /**
- * Write a {@link TimestampedValue} to a parcel so that it can be read using
- * {@link #readFromParcel(Parcel, ClassLoader, Class)}.
- *
- * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
- * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
- * supported by those methods.
- *
- * @param dest the Parcel
- * @param timestampedValue the value
- * @throws RuntimeException if the value could not be written to the Parcel
- */
- public static void writeToParcel(
- @NonNull Parcel dest, @NonNull TimestampedValue<?> timestampedValue) {
- dest.writeLong(timestampedValue.mReferenceTimeMillis);
- dest.writeValue(timestampedValue.mValue);
- }
-
- /**
* Returns the difference in milliseconds between two instance's reference times.
*/
public static long referenceTimeDifference(
@NonNull TimestampedValue<?> one, @NonNull TimestampedValue<?> two) {
return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
}
+
+ public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
+ new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
+
+ @Override
+ public TimestampedValue<?> createFromParcel(@NonNull Parcel source) {
+ return createFromParcel(source, null);
+ }
+
+ @Override
+ public TimestampedValue<?> createFromParcel(
+ @NonNull Parcel source, @Nullable ClassLoader classLoader) {
+ long referenceTimeMillis = source.readLong();
+ Object value = source.readValue(classLoader);
+ return new TimestampedValue<>(referenceTimeMillis, value);
+ }
+
+ @Override
+ public TimestampedValue[] newArray(int size) {
+ return new TimestampedValue[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mReferenceTimeMillis);
+ dest.writeValue(mValue);
+ }
}
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 955be8d..762366e 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -120,4 +120,10 @@
* @see IRecentsAnimationRunner#onCancelled
*/
void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot);
+
+ /**
+ * Sets a state for controller to decide which surface is the destination when the recents
+ * animation is cancelled through fail safe mechanism.
+ */
+ void setWillFinishToHome(boolean willFinishToHome);
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 4b872d3..8bf99ec 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -81,6 +81,14 @@
*/
void showInsets(int types, boolean fromIme);
+ /**
+ * Called when a set of insets source window should be hidden by policy.
+ *
+ * @param types internal inset types (WindowInsets.Type.InsetType) to hide
+ * @param fromIme true if this request originated from IME (InputMethodService).
+ */
+ void hideInsets(int types, boolean fromIme);
+
void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
void dispatchGetNewSurface();
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 1a1d7e6..ad1f201 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -40,8 +40,6 @@
private static final boolean DEBUG = false;
@NonNull
- private final String mName;
- @NonNull
private final InputChannel mInputChannel;
@NonNull
private final IInputMonitorHost mHost;
@@ -81,23 +79,19 @@
- // Code below generated by codegen v1.0.1.
+ // Code below generated by codegen v1.0.7.
//
// DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/InputMonitor.java
- //
- // CHECKSTYLE:OFF Generated code
+
@DataClass.Generated.Member
public InputMonitor(
- @NonNull String name,
@NonNull InputChannel inputChannel,
@NonNull IInputMonitorHost host) {
- this.mName = name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mName);
this.mInputChannel = inputChannel;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mInputChannel);
@@ -109,11 +103,6 @@
}
@DataClass.Generated.Member
- public @NonNull String getName() {
- return mName;
- }
-
- @DataClass.Generated.Member
public @NonNull InputChannel getInputChannel() {
return mInputChannel;
}
@@ -130,7 +119,6 @@
// String fieldNameToString() { ... }
return "InputMonitor { " +
- "name = " + mName + ", " +
"inputChannel = " + mInputChannel + ", " +
"host = " + mHost +
" }";
@@ -142,7 +130,6 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- dest.writeString(mName);
dest.writeTypedObject(mInputChannel, flags);
dest.writeStrongInterface(mHost);
}
@@ -151,6 +138,26 @@
@DataClass.Generated.Member
public int describeContents() { return 0; }
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InputMonitor(Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ InputChannel inputChannel = (InputChannel) in.readTypedObject(InputChannel.CREATOR);
+ IInputMonitorHost host = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
+
+ this.mInputChannel = inputChannel;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInputChannel);
+ this.mHost = host;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHost);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<InputMonitor> CREATOR
= new Parcelable.Creator<InputMonitor>() {
@@ -160,26 +167,16 @@
}
@Override
- @SuppressWarnings({"unchecked", "RedundantCast"})
public InputMonitor createFromParcel(Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- String name = in.readString();
- InputChannel inputChannel = (InputChannel) in.readTypedObject(InputChannel.CREATOR);
- IInputMonitorHost host = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
- return new InputMonitor(
- name,
- inputChannel,
- host);
+ return new InputMonitor(in);
}
};
@DataClass.Generated(
- time = 1569871940995L,
- codegenVersion = "1.0.1",
+ time = 1571177265149L,
+ codegenVersion = "1.0.7",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull java.lang.String mName\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 10a9aaa..08e4eeb 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -38,10 +38,8 @@
// The input application handle.
public final InputApplicationHandle inputApplicationHandle;
- // The client window.
- public final IWindow clientWindow;
-
- // The token associated with the window.
+ // The token associates input data with a window and its input channel. The client input
+ // channel and the server input channel will both contain this token.
public IBinder token;
// The window name.
@@ -120,10 +118,8 @@
private native void nativeDispose();
- public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
- IWindow clientWindow, int displayId) {
+ public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) {
this.inputApplicationHandle = inputApplicationHandle;
- this.clientWindow = clientWindow;
this.displayId = displayId;
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 341c214..e4deffa 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -229,6 +229,10 @@
final InsetsSourceConsumer consumer = items.valueAt(i);
final InsetsSource source = mInitialInsetsState.getSource(consumer.getType());
final InsetsSourceControl control = consumer.getControl();
+ if (control == null) {
+ // Control may not be available for consumer yet or revoked.
+ continue;
+ }
final SurfaceControl leash = consumer.getControl().getLeash();
mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 5a8636d..5bb4f63 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -251,6 +251,10 @@
@Override
public void hide(@InsetType int types) {
+ hide(types, false /* fromIme */);
+ }
+
+ void hide(@InsetType int types, boolean fromIme) {
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
@@ -265,7 +269,7 @@
}
typesReady |= InsetsState.toPublicType(consumer.getType());
}
- applyAnimation(typesReady, false /* show */, false /* fromIme */);
+ applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
}
@Override
@@ -331,42 +335,35 @@
boolean isReady = true;
for (int i = internalTypes.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
- // Double check for IME that IME target window has focus.
- if (consumer.getType() != TYPE_IME || consumer.hasWindowFocus()) {
- boolean setVisible = !consumer.isVisible();
- if (setVisible) {
- // Show request
- switch(consumer.requestShow(fromIme)) {
- case ShowResult.SHOW_IMMEDIATELY:
- typesReady |= InsetsState.toPublicType(consumer.getType());
- break;
- case ShowResult.SHOW_DELAYED:
- isReady = false;
- break;
- case ShowResult.SHOW_FAILED:
- // IME cannot be shown (since it didn't have focus), proceed
- // with animation of other types.
- if (mPendingTypesToShow != 0) {
- // remove IME from pending because view no longer has focus.
- mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
- }
- break;
- }
- } else {
- // Hide request
- // TODO: Move notifyHidden() to beginning of the hide animation
- // (when visibility actually changes using hideDirectly()).
- consumer.notifyHidden();
- typesReady |= InsetsState.toPublicType(consumer.getType());
+ boolean setVisible = !consumer.isVisible();
+ if (setVisible) {
+ // Show request
+ switch(consumer.requestShow(fromIme)) {
+ case ShowResult.SHOW_IMMEDIATELY:
+ typesReady |= InsetsState.toPublicType(consumer.getType());
+ break;
+ case ShowResult.SHOW_DELAYED:
+ isReady = false;
+ break;
+ case ShowResult.SHOW_FAILED:
+ // IME cannot be shown (since it didn't have focus), proceed
+ // with animation of other types.
+ if (mPendingTypesToShow != 0) {
+ // remove IME from pending because view no longer has focus.
+ mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
+ }
+ break;
}
- consumers.put(consumer.getType(), consumer);
} else {
- // window doesnt have focus, no-op.
- isReady = false;
- // TODO: Let the calling app know that window has lost focus and
- // show()/hide()/controlWindowInsetsAnimation requests will be ignored.
- typesReady &= ~InsetsState.toPublicType(consumer.getType());
+ // Hide request
+ // TODO: Move notifyHidden() to beginning of the hide animation
+ // (when visibility actually changes using hideDirectly()).
+ if (!fromIme) {
+ consumer.notifyHidden();
+ }
+ typesReady |= InsetsState.toPublicType(consumer.getType());
}
+ consumers.put(consumer.getType(), consumer);
}
return new Pair<>(typesReady, isReady);
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 8a1fd62..ad59ae5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -32,6 +32,7 @@
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -46,6 +47,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,12 +55,12 @@
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.Callable;
-import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -68,6 +70,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
+import java.util.stream.Stream;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -331,11 +334,83 @@
public View findHierarchyView(String className, int hashCode);
}
- private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
- private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
+ private abstract static class PropertyInfo<T extends Annotation,
+ R extends AccessibleObject & Member> {
+
+ public final R member;
+ public final T property;
+ public final String name;
+ public final Class<?> returnType;
+
+ public String entrySuffix = "";
+ public String valueSuffix = "";
+
+ PropertyInfo(Class<T> property, R member, Class<?> returnType) {
+ this.member = member;
+ this.name = member.getName();
+ this.property = member.getAnnotation(property);
+ this.returnType = returnType;
+ }
+
+ public abstract Object invoke(Object target) throws Exception;
+
+ static <T extends Annotation> PropertyInfo<T, ?> forMethod(Method method,
+ Class<T> property) {
+ // Ensure the method return and parameter types can be resolved.
+ try {
+ if ((method.getReturnType() == Void.class)
+ || (method.getParameterTypes().length != 0)) {
+ return null;
+ }
+ } catch (NoClassDefFoundError e) {
+ return null;
+ }
+ if (!method.isAnnotationPresent(property)) {
+ return null;
+ }
+ method.setAccessible(true);
+
+ PropertyInfo info = new MethodPI(method, property);
+ info.entrySuffix = "()";
+ info.valueSuffix = ";";
+ return info;
+ }
+
+ static <T extends Annotation> PropertyInfo<T, ?> forField(Field field, Class<T> property) {
+ if (!field.isAnnotationPresent(property)) {
+ return null;
+ }
+ field.setAccessible(true);
+ return new FieldPI<>(field, property);
+ }
+ }
+
+ private static class MethodPI<T extends Annotation> extends PropertyInfo<T, Method> {
+
+ MethodPI(Method method, Class<T> property) {
+ super(property, method, method.getReturnType());
+ }
+
+ @Override
+ public Object invoke(Object target) throws Exception {
+ return member.invoke(target);
+ }
+ }
+
+ private static class FieldPI<T extends Annotation> extends PropertyInfo<T, Field> {
+
+ FieldPI(Field field, Class<T> property) {
+ super(property, field, field.getType());
+ }
+
+ @Override
+ public Object invoke(Object target) throws Exception {
+ return member.get(target);
+ }
+ }
// Maximum delay in ms after which we stop trying to capture a View's drawing
- private static final int CAPTURE_TIMEOUT = 4000;
+ private static final int CAPTURE_TIMEOUT = 6000;
private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
private static final String REMOTE_COMMAND_DUMP = "DUMP";
@@ -346,9 +421,9 @@
private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
- private static HashMap<Class<?>, Field[]> sFieldsForClasses;
- private static HashMap<Class<?>, Method[]> sMethodsForClasses;
- private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
+ private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
+ private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
+ sCapturedViewProperties;
/**
* @deprecated This enum is now unused
@@ -1157,6 +1232,69 @@
private static void dumpViewHierarchy(Context context, ViewGroup group,
BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+ cacheExportedProperties(group.getClass());
+ if (!skipChildren) {
+ cacheExportedPropertiesForChildren(group);
+ }
+ // Try to use the handler provided by the view
+ Handler handler = group.getHandler();
+ // Fall back on using the main thread
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+
+ if (handler.getLooper() == Looper.myLooper()) {
+ dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+ includeProperties);
+ } else {
+ FutureTask task = new FutureTask(() ->
+ dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+ includeProperties), null);
+ Message msg = Message.obtain(handler, task);
+ msg.setAsynchronous(true);
+ handler.sendMessage(msg);
+ while (true) {
+ try {
+ task.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
+ return;
+ } catch (InterruptedException e) {
+ // try again
+ } catch (ExecutionException | TimeoutException e) {
+ // Something unexpected happened.
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ private static void cacheExportedPropertiesForChildren(ViewGroup group) {
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = group.getChildAt(i);
+ cacheExportedProperties(view.getClass());
+ if (view instanceof ViewGroup) {
+ cacheExportedPropertiesForChildren((ViewGroup) view);
+ }
+ }
+ }
+
+ private static void cacheExportedProperties(Class<?> klass) {
+ if (sExportProperties != null && sExportProperties.containsKey(klass)) {
+ return;
+ }
+ do {
+ for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
+ if (!info.returnType.isPrimitive() && info.property.deepExport()) {
+ cacheExportedProperties(info.returnType);
+ }
+ }
+ klass = klass.getSuperclass();
+ } while (klass != Object.class);
+ }
+
+
+ private static void dumpViewHierarchyOnUIThread(Context context, ViewGroup group,
+ BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
if (!dumpView(context, group, out, level, includeProperties)) {
return;
}
@@ -1169,16 +1307,16 @@
for (int i = 0; i < count; i++) {
final View view = group.getChildAt(i);
if (view instanceof ViewGroup) {
- dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
- includeProperties);
+ dumpViewHierarchyOnUIThread(context, (ViewGroup) view, out, level + 1,
+ skipChildren, includeProperties);
} else {
dumpView(context, view, out, level + 1, includeProperties);
}
if (view.mOverlay != null) {
ViewOverlay overlay = view.getOverlay();
ViewGroup overlayContainer = overlay.mOverlayViewGroup;
- dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
- includeProperties);
+ dumpViewHierarchyOnUIThread(context, overlayContainer, out, level + 2,
+ skipChildren, includeProperties);
}
}
if (group instanceof HierarchyHandler) {
@@ -1212,81 +1350,28 @@
return true;
}
- private static Field[] getExportedPropertyFields(Class<?> klass) {
- if (sFieldsForClasses == null) {
- sFieldsForClasses = new HashMap<Class<?>, Field[]>();
- }
- if (sAnnotations == null) {
- sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
- }
-
- final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
-
- Field[] fields = map.get(klass);
- if (fields != null) {
- return fields;
- }
-
- try {
- final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
- final ArrayList<Field> foundFields = new ArrayList<Field>();
- for (final Field field : declaredFields) {
- // Fields which can't be resolved have a null type.
- if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
- field.setAccessible(true);
- foundFields.add(field);
- sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
- }
- }
- fields = foundFields.toArray(new Field[foundFields.size()]);
- map.put(klass, fields);
- } catch (NoClassDefFoundError e) {
- throw new AssertionError(e);
- }
-
- return fields;
+ private static <T extends Annotation> PropertyInfo<T, ?>[] convertToPropertyInfos(
+ Method[] methods, Field[] fields, Class<T> property) {
+ return Stream.of(Arrays.stream(methods).map(m -> PropertyInfo.forMethod(m, property)),
+ Arrays.stream(fields).map(f -> PropertyInfo.forField(f, property)))
+ .flatMap(Function.identity())
+ .filter(i -> i != null)
+ .toArray(PropertyInfo[]::new);
}
- private static Method[] getExportedPropertyMethods(Class<?> klass) {
- if (sMethodsForClasses == null) {
- sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
+ private static PropertyInfo<ExportedProperty, ?>[] getExportedProperties(Class<?> klass) {
+ if (sExportProperties == null) {
+ sExportProperties = new HashMap<>();
}
- if (sAnnotations == null) {
- sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
+ final HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> map = sExportProperties;
+ PropertyInfo<ExportedProperty, ?>[] properties = sExportProperties.get(klass);
+
+ if (properties == null) {
+ properties = convertToPropertyInfos(klass.getDeclaredMethodsUnchecked(false),
+ klass.getDeclaredFieldsUnchecked(false), ExportedProperty.class);
+ map.put(klass, properties);
}
-
- final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
-
- Method[] methods = map.get(klass);
- if (methods != null) {
- return methods;
- }
-
- methods = klass.getDeclaredMethodsUnchecked(false);
-
- final ArrayList<Method> foundMethods = new ArrayList<Method>();
- for (final Method method : methods) {
- // Ensure the method return and parameter types can be resolved.
- try {
- method.getReturnType();
- method.getParameterTypes();
- } catch (NoClassDefFoundError e) {
- continue;
- }
-
- if (method.getParameterTypes().length == 0 &&
- method.isAnnotationPresent(ExportedProperty.class) &&
- method.getReturnType() != Void.class) {
- method.setAccessible(true);
- foundMethods.add(method);
- sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
- }
- }
-
- methods = foundMethods.toArray(new Method[foundMethods.size()]);
- map.put(klass, methods);
-
- return methods;
+ return properties;
}
private static void dumpViewProperties(Context context, Object view,
@@ -1305,233 +1390,97 @@
Class<?> klass = view.getClass();
do {
- exportFields(context, view, out, klass, prefix);
- exportMethods(context, view, out, klass, prefix);
+ writeExportedProperties(context, view, out, klass, prefix);
klass = klass.getSuperclass();
} while (klass != Object.class);
}
- private static Object callMethodOnAppropriateTheadBlocking(final Method method,
- final Object object) throws IllegalAccessException, InvocationTargetException,
- TimeoutException {
- if (!(object instanceof View)) {
- return method.invoke(object, (Object[]) null);
- }
-
- final View view = (View) object;
- Callable<Object> callable = new Callable<Object>() {
- @Override
- public Object call() throws IllegalAccessException, InvocationTargetException {
- return method.invoke(view, (Object[]) null);
- }
- };
- FutureTask<Object> future = new FutureTask<Object>(callable);
- // Try to use the handler provided by the view
- Handler handler = view.getHandler();
- // Fall back on using the main thread
- if (handler == null) {
- handler = new Handler(android.os.Looper.getMainLooper());
- }
- handler.post(future);
- while (true) {
- try {
- return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
- } catch (ExecutionException e) {
- Throwable t = e.getCause();
- if (t instanceof IllegalAccessException) {
- throw (IllegalAccessException)t;
- }
- if (t instanceof InvocationTargetException) {
- throw (InvocationTargetException)t;
- }
- throw new RuntimeException("Unexpected exception", t);
- } catch (InterruptedException e) {
- // Call get again
- } catch (CancellationException e) {
- throw new RuntimeException("Unexpected cancellation exception", e);
- }
- }
- }
-
private static String formatIntToHexString(int value) {
return "0x" + Integer.toHexString(value).toUpperCase();
}
- private static void exportMethods(Context context, Object view, BufferedWriter out,
+ private static void writeExportedProperties(Context context, Object view, BufferedWriter out,
Class<?> klass, String prefix) throws IOException {
-
- final Method[] methods = getExportedPropertyMethods(klass);
- int count = methods.length;
- for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
//noinspection EmptyCatchBlock
+ Object value;
try {
- Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
- final Class<?> returnType = method.getReturnType();
- final ExportedProperty property = sAnnotations.get(method);
- String categoryPrefix =
- property.category().length() != 0 ? property.category() + ":" : "";
-
- if (returnType == int.class) {
- if (property.resolveId() && context != null) {
- final int id = (Integer) methodValue;
- methodValue = resolveId(context, id);
- } else {
- final FlagToString[] flagsMapping = property.flagMapping();
- if (flagsMapping.length > 0) {
- final int intValue = (Integer) methodValue;
- final String valuePrefix =
- categoryPrefix + prefix + method.getName() + '_';
- exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
- }
-
- final IntToString[] mapping = property.mapping();
- if (mapping.length > 0) {
- final int intValue = (Integer) methodValue;
- boolean mapped = false;
- int mappingCount = mapping.length;
- for (int j = 0; j < mappingCount; j++) {
- final IntToString mapper = mapping[j];
- if (mapper.from() == intValue) {
- methodValue = mapper.to();
- mapped = true;
- break;
- }
- }
-
- if (!mapped) {
- methodValue = intValue;
- }
- }
- }
- } else if (returnType == int[].class) {
- final int[] array = (int[]) methodValue;
- final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
- final String suffix = "()";
-
- exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
-
- continue;
- } else if (returnType == String[].class) {
- final String[] array = (String[]) methodValue;
- if (property.hasAdjacentMapping() && array != null) {
- for (int j = 0; j < array.length; j += 2) {
- if (array[j] != null) {
- writeEntry(out, categoryPrefix + prefix, array[j], "()",
- array[j + 1] == null ? "null" : array[j + 1]);
- }
-
- }
- }
-
- continue;
- } else if (!returnType.isPrimitive()) {
- if (property.deepExport()) {
- dumpViewProperties(context, methodValue, out, prefix + property.prefix());
- continue;
- }
- }
-
- writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
- } catch (IllegalAccessException e) {
- } catch (InvocationTargetException e) {
- } catch (TimeoutException e) {
+ value = info.invoke(view);
+ } catch (Exception e) {
+ // ignore
+ continue;
}
- }
- }
- private static void exportFields(Context context, Object view, BufferedWriter out,
- Class<?> klass, String prefix) throws IOException {
+ String categoryPrefix =
+ info.property.category().length() != 0 ? info.property.category() + ":" : "";
- final Field[] fields = getExportedPropertyFields(klass);
+ if (info.returnType == int.class || info.returnType == byte.class) {
+ if (info.property.resolveId() && context != null) {
+ final int id = (Integer) value;
+ value = resolveId(context, id);
- int count = fields.length;
- for (int i = 0; i < count; i++) {
- final Field field = fields[i];
-
- //noinspection EmptyCatchBlock
- try {
- Object fieldValue = null;
- final Class<?> type = field.getType();
- final ExportedProperty property = sAnnotations.get(field);
- String categoryPrefix =
- property.category().length() != 0 ? property.category() + ":" : "";
-
- if (type == int.class || type == byte.class) {
- if (property.resolveId() && context != null) {
- final int id = field.getInt(view);
- fieldValue = resolveId(context, id);
- } else {
- final FlagToString[] flagsMapping = property.flagMapping();
- if (flagsMapping.length > 0) {
- final int intValue = field.getInt(view);
- final String valuePrefix =
- categoryPrefix + prefix + field.getName() + '_';
- exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
- }
-
- final IntToString[] mapping = property.mapping();
- if (mapping.length > 0) {
- final int intValue = field.getInt(view);
- int mappingCount = mapping.length;
- for (int j = 0; j < mappingCount; j++) {
- final IntToString mapped = mapping[j];
- if (mapped.from() == intValue) {
- fieldValue = mapped.to();
- break;
- }
- }
-
- if (fieldValue == null) {
- fieldValue = intValue;
- }
- }
-
- if (property.formatToHexString()) {
- fieldValue = field.get(view);
- if (type == int.class) {
- fieldValue = formatIntToHexString((Integer) fieldValue);
- } else if (type == byte.class) {
- fieldValue = "0x"
- + HexEncoding.encodeToString((Byte) fieldValue, true);
- }
- }
+ } else if (info.property.formatToHexString()) {
+ if (info.returnType == int.class) {
+ value = formatIntToHexString((Integer) value);
+ } else if (info.returnType == byte.class) {
+ value = "0x"
+ + HexEncoding.encodeToString((Byte) value, true);
}
- } else if (type == int[].class) {
- final int[] array = (int[]) field.get(view);
- final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
- final String suffix = "";
-
- exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
-
- continue;
- } else if (type == String[].class) {
- final String[] array = (String[]) field.get(view);
- if (property.hasAdjacentMapping() && array != null) {
- for (int j = 0; j < array.length; j += 2) {
- if (array[j] != null) {
- writeEntry(out, categoryPrefix + prefix, array[j], "",
- array[j + 1] == null ? "null" : array[j + 1]);
- }
- }
+ } else {
+ final ViewDebug.FlagToString[] flagsMapping = info.property.flagMapping();
+ if (flagsMapping.length > 0) {
+ final int intValue = (Integer) value;
+ final String valuePrefix =
+ categoryPrefix + prefix + info.name + '_';
+ exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
}
- continue;
- } else if (!type.isPrimitive()) {
- if (property.deepExport()) {
- dumpViewProperties(context, field.get(view), out, prefix +
- property.prefix());
- continue;
+ final ViewDebug.IntToString[] mapping = info.property.mapping();
+ if (mapping.length > 0) {
+ final int intValue = (Integer) value;
+ boolean mapped = false;
+ int mappingCount = mapping.length;
+ for (int j = 0; j < mappingCount; j++) {
+ final ViewDebug.IntToString mapper = mapping[j];
+ if (mapper.from() == intValue) {
+ value = mapper.to();
+ mapped = true;
+ break;
+ }
+ }
+
+ if (!mapped) {
+ value = intValue;
+ }
+ }
+ }
+ } else if (info.returnType == int[].class) {
+ final int[] array = (int[]) value;
+ final String valuePrefix = categoryPrefix + prefix + info.name + '_';
+ exportUnrolledArray(context, out, info.property, array, valuePrefix,
+ info.entrySuffix);
+
+ continue;
+ } else if (info.returnType == String[].class) {
+ final String[] array = (String[]) value;
+ if (info.property.hasAdjacentMapping() && array != null) {
+ for (int j = 0; j < array.length; j += 2) {
+ if (array[j] != null) {
+ writeEntry(out, categoryPrefix + prefix, array[j],
+ info.entrySuffix, array[j + 1] == null ? "null" : array[j + 1]);
+ }
}
}
- if (fieldValue == null) {
- fieldValue = field.get(view);
+ continue;
+ } else if (!info.returnType.isPrimitive()) {
+ if (info.property.deepExport()) {
+ dumpViewProperties(context, value, out, prefix + info.property.prefix());
+ continue;
}
-
- writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
- } catch (IllegalAccessException e) {
}
+
+ writeEntry(out, categoryPrefix + prefix, info.name, info.entrySuffix, value);
}
}
@@ -1721,91 +1670,40 @@
}
}
- private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
- if (mCapturedViewFieldsForClasses == null) {
- mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
+ private static PropertyInfo<CapturedViewProperty, ?>[] getCapturedViewProperties(
+ Class<?> klass) {
+ if (sCapturedViewProperties == null) {
+ sCapturedViewProperties = new HashMap<>();
}
- final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
+ final HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]> map =
+ sCapturedViewProperties;
- Field[] fields = map.get(klass);
- if (fields != null) {
- return fields;
+ PropertyInfo<CapturedViewProperty, ?>[] infos = map.get(klass);
+ if (infos == null) {
+ infos = convertToPropertyInfos(klass.getMethods(), klass.getFields(),
+ CapturedViewProperty.class);
+ map.put(klass, infos);
}
-
- final ArrayList<Field> foundFields = new ArrayList<Field>();
- fields = klass.getFields();
-
- int count = fields.length;
- for (int i = 0; i < count; i++) {
- final Field field = fields[i];
- if (field.isAnnotationPresent(CapturedViewProperty.class)) {
- field.setAccessible(true);
- foundFields.add(field);
- }
- }
-
- fields = foundFields.toArray(new Field[foundFields.size()]);
- map.put(klass, fields);
-
- return fields;
+ return infos;
}
- private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
- if (mCapturedViewMethodsForClasses == null) {
- mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
- }
- final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
-
- Method[] methods = map.get(klass);
- if (methods != null) {
- return methods;
- }
-
- final ArrayList<Method> foundMethods = new ArrayList<Method>();
- methods = klass.getMethods();
-
- int count = methods.length;
- for (int i = 0; i < count; i++) {
- final Method method = methods[i];
- if (method.getParameterTypes().length == 0 &&
- method.isAnnotationPresent(CapturedViewProperty.class) &&
- method.getReturnType() != Void.class) {
- method.setAccessible(true);
- foundMethods.add(method);
- }
- }
-
- methods = foundMethods.toArray(new Method[foundMethods.size()]);
- map.put(klass, methods);
-
- return methods;
- }
-
- private static String capturedViewExportMethods(Object obj, Class<?> klass,
- String prefix) {
-
+ private static String exportCapturedViewProperties(Object obj, Class<?> klass, String prefix) {
if (obj == null) {
return "null";
}
StringBuilder sb = new StringBuilder();
- final Method[] methods = capturedViewGetPropertyMethods(klass);
- int count = methods.length;
- for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ for (PropertyInfo<CapturedViewProperty, ?> pi : getCapturedViewProperties(klass)) {
try {
- Object methodValue = method.invoke(obj, (Object[]) null);
- final Class<?> returnType = method.getReturnType();
+ Object methodValue = pi.invoke(obj);
- CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
- if (property.retrieveReturn()) {
+ if (pi.property.retrieveReturn()) {
//we are interested in the second level data only
- sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
+ sb.append(exportCapturedViewProperties(methodValue, pi.returnType,
+ pi.name + "#"));
} else {
- sb.append(prefix);
- sb.append(method.getName());
- sb.append("()=");
+ sb.append(prefix).append(pi.name).append(pi.entrySuffix).append("=");
if (methodValue != null) {
final String value = methodValue.toString().replace("\n", "\\n");
@@ -1813,47 +1711,10 @@
} else {
sb.append("null");
}
- sb.append("; ");
+ sb.append(pi.valueSuffix).append(" ");
}
- } catch (IllegalAccessException e) {
- //Exception IllegalAccess, it is OK here
- //we simply ignore this method
- } catch (InvocationTargetException e) {
- //Exception InvocationTarget, it is OK here
- //we simply ignore this method
- }
- }
- return sb.toString();
- }
-
- private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
- if (obj == null) {
- return "null";
- }
-
- StringBuilder sb = new StringBuilder();
- final Field[] fields = capturedViewGetPropertyFields(klass);
-
- int count = fields.length;
- for (int i = 0; i < count; i++) {
- final Field field = fields[i];
- try {
- Object fieldValue = field.get(obj);
-
- sb.append(prefix);
- sb.append(field.getName());
- sb.append("=");
-
- if (fieldValue != null) {
- final String value = fieldValue.toString().replace("\n", "\\n");
- sb.append(value);
- } else {
- sb.append("null");
- }
- sb.append(' ');
- } catch (IllegalAccessException e) {
- //Exception IllegalAccess, it is OK here
- //we simply ignore this field
+ } catch (Exception e) {
+ //It is OK here, we simply ignore this property
}
}
return sb.toString();
@@ -1869,8 +1730,7 @@
public static void dumpCapturedView(String tag, Object view) {
Class<?> klass = view.getClass();
StringBuilder sb = new StringBuilder(klass.getName() + ": ");
- sb.append(capturedViewExportFields(view, klass, ""));
- sb.append(capturedViewExportMethods(view, klass, ""));
+ sb.append(exportCapturedViewProperties(view, klass, ""));
Log.d(tag, sb.toString());
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9ddd84f..20dc234 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4570,6 +4570,7 @@
private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
private static final int MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED = 33;
private static final int MSG_SHOW_INSETS = 34;
+ private static final int MSG_HIDE_INSETS = 35;
final class ViewRootHandler extends Handler {
@@ -4636,6 +4637,8 @@
return "MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED";
case MSG_SHOW_INSETS:
return "MSG_SHOW_INSETS";
+ case MSG_HIDE_INSETS:
+ return "MSG_HIDE_INSETS";
}
return super.getMessageName(message);
}
@@ -4754,6 +4757,10 @@
mInsetsController.show(msg.arg1, msg.arg2 == 1);
break;
}
+ case MSG_HIDE_INSETS: {
+ mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+ break;
+ }
case MSG_WINDOW_MOVED:
if (mAdded) {
final int w = mWinFrame.width();
@@ -7559,6 +7566,10 @@
mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
}
+ private void hideInsets(@InsetType int types, boolean fromIme) {
+ mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+ }
+
public void dispatchMoved(int newX, int newY) {
if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
if (mTranslator != null) {
@@ -8682,6 +8693,14 @@
}
@Override
+ public void hideInsets(@InsetType int types, boolean fromIme) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.hideInsets(types, fromIme);
+ }
+ }
+
+ @Override
public void moved(int newX, int newY) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4a6ef98..db76bb6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -197,12 +197,6 @@
int TRANSIT_TASK_OPEN_BEHIND = 16;
/**
- * A window in a task is being animated in-place.
- * @hide
- */
- int TRANSIT_TASK_IN_PLACE = 17;
-
- /**
* An activity is being relaunched (e.g. due to configuration change).
* @hide
*/
@@ -286,7 +280,6 @@
TRANSIT_WALLPAPER_INTRA_OPEN,
TRANSIT_WALLPAPER_INTRA_CLOSE,
TRANSIT_TASK_OPEN_BEHIND,
- TRANSIT_TASK_IN_PLACE,
TRANSIT_ACTIVITY_RELAUNCH,
TRANSIT_DOCK_TASK_FROM_RECENTS,
TRANSIT_KEYGUARD_GOING_AWAY,
@@ -1687,8 +1680,9 @@
* to determine its default behavior.
*
* {@hide} */
- @UnsupportedAppUsage
- public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+ @SystemApi
+ @RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW)
+ public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
/**
* Never animate position changes of the window.
@@ -1849,6 +1843,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "SYSTEM_FLAG_" }, value = {
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
})
public @interface SystemFlags {}
@@ -1870,8 +1865,8 @@
equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
name = "WANTS_OFFSET_NOTIFICATIONS"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
- equals = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ mask = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+ equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
name = "SHOW_FOR_ALL_USERS"),
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
diff --git a/core/java/android/webkit/FindAddress.java b/core/java/android/webkit/FindAddress.java
index 9183227..b146e3f6 100644
--- a/core/java/android/webkit/FindAddress.java
+++ b/core/java/android/webkit/FindAddress.java
@@ -154,7 +154,7 @@
// A house number component is "one" or a number, optionally
// followed by a single alphabetic character, or
- private static final String HOUSE_COMPONENT = "(?:one|\\d+([a-z](?=[^a-z]|$)|st|nd|rd|th)?)";
+ private static final String HOUSE_COMPONENT = "(?:one|[0-9]+([a-z](?=[^a-z]|$)|st|nd|rd|th)?)";
// House numbers are a repetition of |HOUSE_COMPONENT|, separated by -, and followed by
// a delimiter character.
@@ -253,10 +253,10 @@
Pattern.CASE_INSENSITIVE);
private static final Pattern sSuffixedNumberRe =
- Pattern.compile("(\\d+)(st|nd|rd|th)", Pattern.CASE_INSENSITIVE);
+ Pattern.compile("([0-9]+)(st|nd|rd|th)", Pattern.CASE_INSENSITIVE);
private static final Pattern sZipCodeRe =
- Pattern.compile("(?:\\d{5}(?:-\\d{4})?)" + WORD_END, Pattern.CASE_INSENSITIVE);
+ Pattern.compile("(?:[0-9]{5}(?:-[0-9]{4})?)" + WORD_END, Pattern.CASE_INSENSITIVE);
private static boolean checkHouseNumber(String houseNumber) {
// Make sure that there are at most 5 digits.
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 925a589..0b15cd0 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -246,7 +246,7 @@
Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
mContext, toastMessage, Toast.LENGTH_LONG);
warningToast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
warningToast.show();
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 407a85f..068056f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -388,21 +388,24 @@
mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
mSystemWindowInsets.right, 0);
- View emptyView = findViewById(R.id.empty);
- if (emptyView != null) {
- emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
- + getResources().getDimensionPixelSize(
- R.dimen.chooser_edge_margin_normal) * 2);
- }
-
- if (mFooterSpacer == null) {
- mFooterSpacer = new Space(getApplicationContext());
+ // Need extra padding so the list can fully scroll up
+ if (useLayoutWithDefault()) {
+ if (mFooterSpacer == null) {
+ mFooterSpacer = new Space(getApplicationContext());
+ } else {
+ ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ }
+ mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
+ mSystemWindowInsets.bottom));
+ ((ListView) mAdapterView).addFooterView(mFooterSpacer);
} else {
- ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ View emptyView = findViewById(R.id.empty);
+ if (emptyView != null) {
+ emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+ + getResources().getDimensionPixelSize(
+ R.dimen.chooser_edge_margin_normal) * 2);
+ }
}
- mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
- mSystemWindowInsets.bottom));
- ((ListView) mAdapterView).addFooterView(mFooterSpacer);
resetButtonBar();
@@ -561,7 +564,7 @@
intent.getData().getHost(),
mAdapter.getFilteredItem().getDisplayLabel());
} else if (mAdapter.areAllTargetsBrowsers()) {
- dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
+ dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
} else {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
intent.getData().getHost());
@@ -1304,6 +1307,7 @@
// In case this method is called again (due to activity recreation), avoid adding a new
// header if one is already present.
if (useHeader && listView != null && listView.getHeaderViewsCount() == 0) {
+ listView.setHeaderDividersEnabled(true);
listView.addHeaderView(LayoutInflater.from(this).inflate(
R.layout.resolver_different_item_header, listView, false));
}
@@ -1346,11 +1350,13 @@
final ViewGroup buttonLayout = findViewById(R.id.button_bar);
if (buttonLayout != null) {
buttonLayout.setVisibility(View.VISIBLE);
- int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
- buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
- buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
- R.dimen.resolver_button_bar_spacing) + inset);
+ if (!useLayoutWithDefault()) {
+ int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+ buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+ buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+ R.dimen.resolver_button_bar_spacing) + inset);
+ }
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
@@ -2057,7 +2063,9 @@
CharSequence subLabel = info.getExtendedInfo();
if (TextUtils.equals(label, subLabel)) subLabel = null;
- if (!TextUtils.equals(holder.text2.getText(), subLabel)) {
+ if (!TextUtils.equals(holder.text2.getText(), subLabel)
+ && !TextUtils.isEmpty(subLabel)) {
+ holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(subLabel);
}
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 0ed2524..1d4239f 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -950,6 +950,14 @@
proto.write(PackageAssociationProcessStatsProto.COMPONENT_NAME, mName);
+ proto.write(PackageAssociationProcessStatsProto.TOTAL_COUNT, mTotalCount);
+ proto.write(PackageAssociationProcessStatsProto.TOTAL_DURATION_MS, getTotalDuration(now));
+ if (mTotalActiveCount != 0) {
+ proto.write(PackageAssociationProcessStatsProto.ACTIVE_COUNT, mTotalActiveCount);
+ proto.write(PackageAssociationProcessStatsProto.ACTIVE_DURATION_MS,
+ getActiveDuration(now));
+ }
+
final int NSRC = mSources.size();
for (int isrc = 0; isrc < NSRC; isrc++) {
final SourceKey key = mSources.keyAt(isrc);
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl
new file mode 100644
index 0000000..434c1b8
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+parcelable CompatibilityChangeConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.java b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
new file mode 100644
index 0000000..fd2ada0
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+
+import android.compat.Compatibility.ChangeConfig;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Parcelable containing compat config overrides for a given application.
+ * @hide
+ */
+public final class CompatibilityChangeConfig implements Parcelable {
+ private final ChangeConfig mChangeConfig;
+
+ public CompatibilityChangeConfig(ChangeConfig changeConfig) {
+ mChangeConfig = changeConfig;
+ }
+
+ /**
+ * Changes forced to be enabled.
+ */
+ public Set<Long> enabledChanges() {
+ return mChangeConfig.forceEnabledSet();
+ }
+
+ /**
+ * Changes forced to be disabled.
+ */
+ public Set<Long> disabledChanges() {
+ return mChangeConfig.forceDisabledSet();
+ }
+
+ private CompatibilityChangeConfig(Parcel in) {
+ long[] enabledArray = in.createLongArray();
+ long[] disabledArray = in.createLongArray();
+ Set<Long> enabled = toLongSet(enabledArray);
+ Set<Long> disabled = toLongSet(disabledArray);
+ mChangeConfig = new ChangeConfig(enabled, disabled);
+ }
+
+ private static Set<Long> toLongSet(long[] values) {
+ Set<Long> ret = new HashSet<>();
+ for (long value: values) {
+ ret.add(value);
+ }
+ return ret;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ long[] enabled = mChangeConfig.forceEnabledChangesArray();
+ long[] disabled = mChangeConfig.forceDisabledChangesArray();
+
+ dest.writeLongArray(enabled);
+ dest.writeLongArray(disabled);
+ }
+
+ public static final Parcelable.Creator<CompatibilityChangeConfig> CREATOR =
+ new Parcelable.Creator<CompatibilityChangeConfig>() {
+
+ @Override
+ public CompatibilityChangeConfig createFromParcel(Parcel in) {
+ return new CompatibilityChangeConfig(in);
+ }
+
+ @Override
+ public CompatibilityChangeConfig[] newArray(int size) {
+ return new CompatibilityChangeConfig[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 4d8378a..4099cfa 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -18,6 +18,8 @@
import android.content.pm.ApplicationInfo;
+parcelable CompatibilityChangeConfig;
+
/**
* Platform private API for talking with the PlatformCompat service.
*
@@ -125,4 +127,21 @@
* @return {@code true} if the change is enabled for the current app.
*/
boolean isChangeEnabledByUid(long changeId, int uid);
-}
\ No newline at end of file
+
+ /**
+ * Add overrides to compatibility changes.
+ *
+ * @param overrides Parcelable containing the compat change overrides to be applied.
+ * @param packageName The package name of the app whose changes will be overridden.
+ *
+ */
+ void setOverrides(in CompatibilityChangeConfig overrides, in String packageName);
+
+ /**
+ * Revert overrides to compatibility changes.
+ *
+ * @param packageName The package name of the app whose overrides will be cleared.
+ *
+ */
+ void clearOverrides(in String packageName);
+}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index cdb79ab..f5708a5c 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -17,6 +17,7 @@
package com.android.internal.content;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Intent;
@@ -552,6 +553,11 @@
flags |= Document.FLAG_SUPPORTS_DELETE;
flags |= Document.FLAG_SUPPORTS_RENAME;
flags |= Document.FLAG_SUPPORTS_MOVE;
+
+ if (shouldBlockFromTree(docId)) {
+ flags |= Document.FLAG_DIR_BLOCKS_TREE;
+ }
+
} else {
flags |= Document.FLAG_SUPPORTS_WRITE;
flags |= Document.FLAG_SUPPORTS_DELETE;
@@ -592,6 +598,10 @@
return row;
}
+ protected boolean shouldBlockFromTree(@NonNull String docId) {
+ return false;
+ }
+
protected boolean typeSupportsMetadata(String mimeType) {
return MetadataReader.isSupportedMimeType(mimeType)
|| Document.MIME_TYPE_DIR.equals(mimeType);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 158700b..363e549 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -184,6 +184,12 @@
System.loadLibrary("android");
System.loadLibrary("compiler_rt");
System.loadLibrary("jnigraphics");
+
+ try {
+ System.loadLibrary("sfplugin_ccodec");
+ } catch (Error | RuntimeException e) {
+ // tolerate missing sfplugin_ccodec which is only present on Codec 2 devices
+ }
}
native private static void nativePreloadAppProcessHALs();
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index a845b58..659134a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -17,6 +17,7 @@
package com.android.internal.statusbar;
import android.app.Notification;
+import android.net.Uri;
import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
@@ -77,6 +78,7 @@
void onNotificationSettingsViewed(String key);
void setSystemUiVisibility(int displayId, int vis, int mask, String cause);
void onNotificationBubbleChanged(String key, boolean isBubble);
+ void grantInlineReplyUriPermission(String key, in Uri uri);
void onGlobalActionsShown();
void onGlobalActionsHidden();
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 0e078dd..7e1f13a 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -80,6 +80,10 @@
}
@Override
+ public void hideInsets(@InsetType int types, boolean fromIme) throws RemoteException {
+ }
+
+ @Override
public void moved(int newX, int newY) {
}
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 09bc28c..85a45fd 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -1,13 +1,9 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
import android.os.AsyncTask;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Helper class to check/verify PIN/Password/Pattern asynchronously.
*/
@@ -53,34 +49,28 @@
}
/**
- * Verify a pattern asynchronously.
+ * Verify a lockscreen credential asynchronously.
*
* @param utils The LockPatternUtils instance to use.
- * @param pattern The pattern to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
+ * @param credential The credential to check.
+ * @param challenge The challenge to verify against the credential.
+ * @param userId The user to check against the credential.
* @param callback The callback to be invoked with the verification result.
*/
- public static AsyncTask<?, ?, ?> verifyPattern(final LockPatternUtils utils,
- final List<LockPatternView.Cell> pattern,
+ public static AsyncTask<?, ?, ?> verifyCredential(final LockPatternUtils utils,
+ final LockscreenCredential credential,
final long challenge,
final int userId,
final OnVerifyCallback callback) {
+ // Create a copy of the credential since checking credential is asynchrounous.
+ final LockscreenCredential credentialCopy = credential.duplicate();
AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
private int mThrottleTimeout;
- private List<LockPatternView.Cell> patternCopy;
-
- @Override
- protected void onPreExecute() {
- // Make a copy of the pattern to prevent race conditions.
- // No need to clone the individual cells because they are immutable.
- patternCopy = new ArrayList(pattern);
- }
@Override
protected byte[] doInBackground(Void... args) {
try {
- return utils.verifyPattern(patternCopy, challenge, userId);
+ return utils.verifyCredential(credentialCopy, challenge, userId);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return null;
@@ -90,6 +80,12 @@
@Override
protected void onPostExecute(byte[] result) {
callback.onVerified(result, mThrottleTimeout);
+ credentialCopy.zeroize();
+ }
+
+ @Override
+ protected void onCancelled() {
+ credentialCopy.zeroize();
}
};
task.execute();
@@ -97,32 +93,26 @@
}
/**
- * Checks a pattern asynchronously.
+ * Checks a lockscreen credential asynchronously.
*
* @param utils The LockPatternUtils instance to use.
- * @param pattern The pattern to check.
- * @param userId The user to check against the pattern.
+ * @param credential The credential to check.
+ * @param userId The user to check against the credential.
* @param callback The callback to be invoked with the check result.
*/
- public static AsyncTask<?, ?, ?> checkPattern(final LockPatternUtils utils,
- final List<LockPatternView.Cell> pattern,
+ public static AsyncTask<?, ?, ?> checkCredential(final LockPatternUtils utils,
+ final LockscreenCredential credential,
final int userId,
final OnCheckCallback callback) {
+ // Create a copy of the credential since checking credential is asynchrounous.
+ final LockscreenCredential credentialCopy = credential.duplicate();
AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
private int mThrottleTimeout;
- private List<LockPatternView.Cell> patternCopy;
-
- @Override
- protected void onPreExecute() {
- // Make a copy of the pattern to prevent race conditions.
- // No need to clone the individual cells because they are immutable.
- patternCopy = new ArrayList(pattern);
- }
@Override
protected Boolean doInBackground(Void... args) {
try {
- return utils.checkPattern(patternCopy, userId, callback::onEarlyMatched);
+ return utils.checkCredential(credentialCopy, userId, callback::onEarlyMatched);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return false;
@@ -132,11 +122,13 @@
@Override
protected void onPostExecute(Boolean result) {
callback.onChecked(result, mThrottleTimeout);
+ credentialCopy.zeroize();
}
@Override
protected void onCancelled() {
callback.onCancelled();
+ credentialCopy.zeroize();
}
};
task.execute();
@@ -144,84 +136,29 @@
}
/**
- * Verify a password asynchronously.
+ * Perform a lockscreen credential verification explicitly on a managed profile with unified
+ * challenge, using the parent user's credential.
*
* @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the verification result.
- *
- * @deprecated Pass the password as a byte array.
- */
- @Deprecated
- public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
- final String password,
- final long challenge,
- final int userId,
- final OnVerifyCallback callback) {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- return verifyPassword(utils, passwordBytes, challenge, userId, callback);
- }
-
- /**
- * Verify a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the verification result.
- */
- public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
- final byte[] password,
- final long challenge,
- final int userId,
- final OnVerifyCallback callback) {
- AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
- private int mThrottleTimeout;
-
- @Override
- protected byte[] doInBackground(Void... args) {
- try {
- return utils.verifyPassword(password, challenge, userId);
- } catch (RequestThrottledException ex) {
- mThrottleTimeout = ex.getTimeoutMs();
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(byte[] result) {
- callback.onVerified(result, mThrottleTimeout);
- }
- };
- task.execute();
- return task;
- }
-
- /**
- * Verify a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
+ * @param credential The credential to check.
+ * @param challenge The challenge to verify against the credential.
+ * @param userId The user to check against the credential.
* @param callback The callback to be invoked with the verification result.
*/
public static AsyncTask<?, ?, ?> verifyTiedProfileChallenge(final LockPatternUtils utils,
- final byte[] password,
- final boolean isPattern,
+ final LockscreenCredential credential,
final long challenge,
final int userId,
final OnVerifyCallback callback) {
+ // Create a copy of the credential since checking credential is asynchrounous.
+ final LockscreenCredential credentialCopy = credential.duplicate();
AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
private int mThrottleTimeout;
@Override
protected byte[] doInBackground(Void... args) {
try {
- return utils.verifyTiedProfileChallenge(password, isPattern, challenge, userId);
+ return utils.verifyTiedProfileChallenge(credentialCopy, challenge, userId);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return null;
@@ -231,64 +168,12 @@
@Override
protected void onPostExecute(byte[] result) {
callback.onVerified(result, mThrottleTimeout);
- }
- };
- task.execute();
- return task;
- }
-
- /**
- * Checks a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the check result.
- * @deprecated Pass passwords as byte[]
- */
- @UnsupportedAppUsage
- @Deprecated
- public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
- final String password,
- final int userId,
- final OnCheckCallback callback) {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- return checkPassword(utils, passwordBytes, userId, callback);
- }
-
- /**
- * Checks a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param passwordBytes The password to check.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the check result.
- */
- public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
- final byte[] passwordBytes,
- final int userId,
- final OnCheckCallback callback) {
- AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
- private int mThrottleTimeout;
-
- @Override
- protected Boolean doInBackground(Void... args) {
- try {
- return utils.checkPassword(passwordBytes, userId, callback::onEarlyMatched);
- } catch (RequestThrottledException ex) {
- mThrottleTimeout = ex.getTimeoutMs();
- return false;
- }
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- callback.onChecked(result, mThrottleTimeout);
+ credentialCopy.zeroize();
}
@Override
protected void onCancelled() {
- callback.onCancelled();
+ credentialCopy.zeroize();
}
};
task.execute();
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 070121c..8fea703 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -26,6 +26,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.admin.DevicePolicyManager;
@@ -58,10 +59,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.google.android.collect.Lists;
-
import libcore.util.HexEncoding;
+import com.google.android.collect.Lists;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
@@ -77,7 +78,6 @@
* Utilities for the lock pattern and its settings.
*/
public class LockPatternUtils {
-
private static final String TAG = "LockPatternUtils";
private static final boolean FRP_CREDENTIAL_ENABLED = true;
@@ -114,6 +114,7 @@
*/
public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE;
+ // NOTE: When modifying this, make sure credential sufficiency validation logic is intact.
public static final int CREDENTIAL_TYPE_NONE = -1;
public static final int CREDENTIAL_TYPE_PATTERN = 1;
public static final int CREDENTIAL_TYPE_PASSWORD = 2;
@@ -289,10 +290,10 @@
return getDevicePolicyManager().getPasswordMaximumLength(quality);
}
- /**
- * Gets the device policy password mode. If the mode is non-specific, returns
- * MODE_PATTERN which allows the user to choose anything.
- */
+ public PasswordMetrics getRequestedPasswordMetrics(int userId) {
+ return getDevicePolicyManager().getPasswordMinimumMetrics(userId);
+ }
+
public int getRequestedPasswordQuality(int userId) {
return getDevicePolicyManager().getPasswordQuality(null, userId);
}
@@ -365,11 +366,24 @@
null /* componentName */, userId);
}
- private byte[] verifyCredential(byte[] credential, int type, long challenge, int userId)
- throws RequestThrottledException {
+ /**
+ * Check to see if a credential matches the saved one.
+ * If credential matches, return an opaque attestation that the challenge was verified.
+ *
+ * @param credential The credential to check.
+ * @param challenge The challenge to verify against the credential
+ * @return the attestation that the challenge was verified, or null
+ * @param userId The user whose credential is being verified
+ * @throws RequestThrottledException if credential verification is being throttled due to
+ * to many incorrect attempts.
+ * @throws IllegalStateException if called on the main thread.
+ */
+ public byte[] verifyCredential(@NonNull LockscreenCredential credential, long challenge,
+ int userId) throws RequestThrottledException {
+ throwIfCalledOnMainThread();
try {
- VerifyCredentialResponse response = getLockSettings().verifyCredential(credential,
- type, challenge, userId);
+ VerifyCredentialResponse response = getLockSettings().verifyCredential(
+ credential.getCredential(), credential.getType(), challenge, userId);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return response.getPayload();
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
@@ -382,11 +396,24 @@
}
}
- private boolean checkCredential(byte[] credential, int type, int userId,
+ /**
+ * Check to see if a credential matches the saved one.
+ *
+ * @param credential The credential to check.
+ * @param userId The user whose credential is being checked
+ * @param progressCallback callback to deliver early signal that the credential matches
+ * @return {@code true} if credential matches, {@code false} otherwise
+ * @throws RequestThrottledException if credential verification is being throttled due to
+ * to many incorrect attempts.
+ * @throws IllegalStateException if called on the main thread.
+ */
+ public boolean checkCredential(@NonNull LockscreenCredential credential, int userId,
@Nullable CheckCredentialProgressCallback progressCallback)
throws RequestThrottledException {
+ throwIfCalledOnMainThread();
try {
- VerifyCredentialResponse response = getLockSettings().checkCredential(credential, type,
+ VerifyCredentialResponse response = getLockSettings().checkCredential(
+ credential.getCredential(), credential.getType(),
userId, wrapCallback(progressCallback));
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -402,79 +429,26 @@
}
/**
- * Check to see if a pattern matches the saved pattern.
- * If pattern matches, return an opaque attestation that the challenge
- * was verified.
+ * Check if the credential of a managed profile with unified challenge matches. In this context,
+ * The credential should be the parent user's lockscreen password. If credential matches,
+ * return an opaque attestation associated with the managed profile that the challenge was
+ * verified.
*
- * @param pattern The pattern to check.
- * @param challenge The challenge to verify against the pattern
- * @return the attestation that the challenge was verified, or null.
+ * @param credential The parent user's credential to check.
+ * @param challenge The challenge to verify against the credential
+ * @return the attestation that the challenge was verified, or null
+ * @param userId The managed profile user id
+ * @throws RequestThrottledException if credential verification is being throttled due to
+ * to many incorrect attempts.
+ * @throws IllegalStateException if called on the main thread.
*/
- public byte[] verifyPattern(List<LockPatternView.Cell> pattern, long challenge, int userId)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return verifyCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, challenge,
- userId);
- }
-
- /**
- * Check to see if a pattern matches the saved pattern. If no pattern exists,
- * always returns true.
- * @param pattern The pattern to check.
- * @return Whether the pattern matches the stored one.
- */
- public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId)
- throws RequestThrottledException {
- return checkPattern(pattern, userId, null /* progressCallback */);
- }
-
- /**
- * Check to see if a pattern matches the saved pattern. If no pattern exists,
- * always returns true.
- * @param pattern The pattern to check.
- * @return Whether the pattern matches the stored one.
- */
- public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId,
- @Nullable CheckCredentialProgressCallback progressCallback)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return checkCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, userId,
- progressCallback);
- }
-
- /**
- * Check to see if a password matches the saved password.
- * If password matches, return an opaque attestation that the challenge
- * was verified.
- *
- * @param password The password to check.
- * @param challenge The challenge to verify against the password
- * @return the attestation that the challenge was verified, or null.
- */
- public byte[] verifyPassword(byte[] password, long challenge, int userId)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return verifyCredential(password, CREDENTIAL_TYPE_PASSWORD, challenge, userId);
- }
-
-
- /**
- * Check to see if a password matches the saved password.
- * If password matches, return an opaque attestation that the challenge
- * was verified.
- *
- * @param password The password to check.
- * @param challenge The challenge to verify against the password
- * @return the attestation that the challenge was verified, or null.
- */
- public byte[] verifyTiedProfileChallenge(byte[] password, boolean isPattern, long challenge,
- int userId) throws RequestThrottledException {
+ public byte[] verifyTiedProfileChallenge(@NonNull LockscreenCredential credential,
+ long challenge, int userId) throws RequestThrottledException {
throwIfCalledOnMainThread();
try {
VerifyCredentialResponse response =
- getLockSettings().verifyTiedProfileChallenge(password,
- isPattern ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_PASSWORD, challenge,
- userId);
+ getLockSettings().verifyTiedProfileChallenge(
+ credential.getCredential(), credential.getType(), challenge, userId);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return response.getPayload();
@@ -489,61 +463,6 @@
}
/**
- *
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
- @UnsupportedAppUsage
- public boolean checkPassword(String password, int userId) throws RequestThrottledException {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- return checkPassword(passwordBytes, userId, null /* progressCallback */);
- }
-
-
- /**
- *
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
- public boolean checkPassword(byte[] password, int userId) throws RequestThrottledException {
- return checkPassword(password, userId, null /* progressCallback */);
- }
-
- // TODO(b/120484642): This method is necessary for vendor/qcom code and is a hidden api
- /* *
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
- public boolean checkPassword(String password, int userId,
- @Nullable CheckCredentialProgressCallback progressCallback)
- throws RequestThrottledException {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- throwIfCalledOnMainThread();
- return checkCredential(passwordBytes, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback);
-
- }
-
- /**
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
-
- public boolean checkPassword(byte[] password, int userId,
- @Nullable CheckCredentialProgressCallback progressCallback)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return checkCredential(password, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback);
- }
-
- /**
* Check to see if vold already has the password.
* Note that this also clears vold's copy of the password.
* @return Whether the vold password matches or not.
@@ -560,9 +479,10 @@
* Returns the password history hash factor, needed to check new password against password
* history with {@link #checkPasswordHistory(byte[], byte[], int)}
*/
- public byte[] getPasswordHistoryHashFactor(byte[] currentPassword, int userId) {
+ public byte[] getPasswordHistoryHashFactor(@NonNull LockscreenCredential currentPassword,
+ int userId) {
try {
- return getLockSettings().getHashFactor(currentPassword, userId);
+ return getLockSettings().getHashFactor(currentPassword.getCredential(), userId);
} catch (RemoteException e) {
Log.e(TAG, "failed to get hash factor", e);
return null;
@@ -679,57 +599,6 @@
}
/**
- * Clear any lock pattern or password.
-
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param savedCredential The previously saved credential
- * @param userHandle the user whose pattern is to be saved.
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean clearLock(byte[] savedCredential, int userHandle) {
- return clearLock(savedCredential, userHandle, false);
- }
-
- /**
- * Clear any lock pattern or password, with the option to ignore incorrect existing credential.
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param savedCredential The previously saved credential
- * @param userHandle the user whose pattern is to be saved.
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean clearLock(byte[] savedCredential, int userHandle, boolean allowUntrustedChange) {
- final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
- setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userHandle);
-
- try {
- if (!getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
- PASSWORD_QUALITY_UNSPECIFIED, userHandle, allowUntrustedChange)) {
- return false;
- }
- } catch (RemoteException | RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
- throw new RuntimeException("Failed to clear lock", e);
- }
-
- if (userHandle == UserHandle.USER_SYSTEM) {
- // Set the encryption password to default.
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- setCredentialRequiredToDecrypt(false);
- }
-
- onAfterChangingPassword(userHandle);
- return true;
- }
-
- /**
* Disable showing lock screen at all for a given user.
* This is only meaningful if pattern, pin or password are not set.
*
@@ -762,75 +631,88 @@
|| isDemoUser;
}
- /**
- * Save a lock pattern.
- *
- * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
- * is incorrect, or if the lockscreen verification is still being throttled.
- *
- * @param pattern The new pattern to save.
- * @param savedPattern The previously saved pattern, converted to byte[] format
- * @param userId the user whose pattern is to be saved.
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
- int userId) {
- return saveLockPattern(pattern, savedPattern, userId, false);
+ /** Returns if the given quality maps to an alphabetic password */
+ public static boolean isQualityAlphabeticPassword(int quality) {
+ return quality >= PASSWORD_QUALITY_ALPHABETIC;
+ }
+
+ /** Returns if the given quality maps to an numeric pin */
+ public static boolean isQualityNumericPin(int quality) {
+ return quality == PASSWORD_QUALITY_NUMERIC || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
}
/**
- * Save a lock pattern.
+ * Save a new lockscreen credential.
*
- * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
- * is incorrect, or if the lockscreen verification is still being throttled.
+ * <p> This method will fail (returning {@code false}) if the previously saved credential
+ * provided is incorrect, or if the lockscreen verification is still being throttled.
*
- * @param pattern The new pattern to save.
- * @param savedPattern The previously saved pattern, converted to byte[] format
- * @param userId the user whose pattern is to be saved.
- * @param allowUntrustedChange whether we want to allow saving a new password if the existing
- * password being provided is incorrect.
- * @return whether this was successful or not.
+ * @param newCredential The new credential to save
+ * @param savedCredential The current credential
+ * @param userId the user whose lockscreen credential is to be changed
+ *
+ * @return whether this method saved the new password successfully or not. This flow will fail
+ * and return false if the given credential is wrong.
* @throws RuntimeException if password change encountered an unrecoverable error.
*/
- public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
- int userId, boolean allowUntrustedChange) {
+ public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
+ @NonNull LockscreenCredential savedCredential, int userId) {
+ return setLockCredential(newCredential, savedCredential, userId, false);
+ }
+
+ /**
+ * Save a new lockscreen credential.
+ * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
+ * is incorrect and allowUntrustedChange is false, or if the lockscreen verification is still
+ * being throttled.
+ * @param newCredential The new credential to save
+ * @param savedCredential The current credential
+ * @param userHandle the user whose lockscreen credential is to be changed
+ * @param allowUntrustedChange whether we want to allow saving a new pattern if the existing
+ * credentialt being provided is incorrect.
+ *
+ * @return whether this method saved the new password successfully or not. This flow will fail
+ * and return false if the given credential is wrong and allowUntrustedChange is false.
+ * @throws RuntimeException if password change encountered an unrecoverable error.
+ */
+ public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
+ @NonNull LockscreenCredential savedCredential, int userHandle,
+ boolean allowUntrustedChange) {
if (!hasSecureLockScreen()) {
throw new UnsupportedOperationException(
"This operation requires the lock screen feature.");
}
- if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
- throw new IllegalArgumentException("pattern must not be null and at least "
- + MIN_LOCK_PATTERN_SIZE + " dots long.");
- }
+ newCredential.checkLength();
- final byte[] bytePattern = patternToByteArray(pattern);
- final int currentQuality = getKeyguardStoredPasswordQuality(userId);
- setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_SOMETHING, userId);
+ final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
+ setKeyguardStoredPasswordQuality(newCredential.getQuality(), userHandle);
+
try {
- if (!getLockSettings().setLockCredential(bytePattern, CREDENTIAL_TYPE_PATTERN,
- savedPattern, PASSWORD_QUALITY_SOMETHING, userId, allowUntrustedChange)) {
+ if (!getLockSettings().setLockCredential(
+ newCredential.getCredential(), newCredential.getType(),
+ savedCredential.getCredential(),
+ newCredential.getQuality(), userHandle, allowUntrustedChange)) {
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
return false;
}
} catch (RemoteException | RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userId);
- throw new RuntimeException("Couldn't save lock pattern", e);
- }
- // Update the device encryption password.
- if (userId == UserHandle.USER_SYSTEM
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- if (!shouldEncryptWithCredentials(true)) {
- clearEncryptionPassword();
- } else {
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, bytePattern);
- }
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+ throw new RuntimeException("Unable to save lock password", e);
}
- reportPatternWasChosen(userId);
- onAfterChangingPassword(userId);
+ onPostPasswordChanged(newCredential, userHandle);
return true;
}
+ private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
+ updateEncryptionPasswordIfNeeded(newCredential, userHandle);
+ if (newCredential.isPattern()) {
+ reportPatternWasChosen(userHandle);
+ }
+ updatePasswordHistory(newCredential, userHandle);
+ reportEnabledTrustAgentsChanged(userHandle);
+ }
+
private void updateCryptoUserInfo(int userId) {
if (userId != UserHandle.USER_SYSTEM) {
return;
@@ -929,149 +811,35 @@
}
/**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * password.
- *
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param password The password to save
- * @param savedPassword The previously saved lock password, or null if none
- * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
- * android.content.ComponentName)}
- * @param userHandle The userId of the user to change the password for
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- * @deprecated Pass password as a byte array
- */
- @Deprecated
- public boolean saveLockPassword(String password, String savedPassword, int requestedQuality,
- int userHandle) {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- byte[] savedPasswordBytes = savedPassword != null ? savedPassword.getBytes() : null;
- return saveLockPassword(passwordBytes, savedPasswordBytes, requestedQuality, userHandle);
- }
-
- /**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * password.
- *
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param password The password to save
- * @param savedPassword The previously saved lock password, or null if none
- * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
- * android.content.ComponentName)}
- * @param userHandle The userId of the user to change the password for
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean saveLockPassword(byte[] password, byte[] savedPassword, int requestedQuality,
- int userHandle) {
- return saveLockPassword(password, savedPassword, requestedQuality,
- userHandle, false);
- }
-
- /**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * password.
- *
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param password The password to save
- * @param savedPassword The previously saved lock password, or null if none
- * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
- * android.content.ComponentName)}
- * @param userHandle The userId of the user to change the password for
- * @param allowUntrustedChange whether we want to allow saving a new password if the existing
- * password being provided is incorrect.
- * @return whether this method saved the new password successfully or not. This flow will fail
- * and return false if the given credential is wrong and allowUntrustedChange is false.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean saveLockPassword(byte[] password, byte[] savedPassword,
- int requestedQuality, int userHandle, boolean allowUntrustedChange) {
- if (!hasSecureLockScreen()) {
- throw new UnsupportedOperationException(
- "This operation requires the lock screen feature.");
- }
- if (password == null || password.length < MIN_LOCK_PASSWORD_SIZE) {
- throw new IllegalArgumentException("password must not be null and at least "
- + "of length " + MIN_LOCK_PASSWORD_SIZE);
- }
-
- if (requestedQuality < PASSWORD_QUALITY_NUMERIC) {
- throw new IllegalArgumentException("quality must be at least NUMERIC, but was "
- + requestedQuality);
- }
-
- final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
- final int passwordQuality = PasswordMetrics.computeForPassword(password).quality;
- final int newKeyguardQuality =
- computeKeyguardQuality(CREDENTIAL_TYPE_PASSWORD, requestedQuality, passwordQuality);
- setKeyguardStoredPasswordQuality(newKeyguardQuality, userHandle);
- try {
- getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
- requestedQuality, userHandle, allowUntrustedChange);
- } catch (RemoteException | RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
- throw new RuntimeException("Unable to save lock password", e);
- }
-
- updateEncryptionPasswordIfNeeded(password, passwordQuality, userHandle);
- updatePasswordHistory(password, userHandle);
- onAfterChangingPassword(userHandle);
- return true;
- }
-
- /**
- * Compute keyguard credential quality to store in PASSWORD_TYPE_KEY by computing max between
- * them so that digit-only password is distinguished from PIN.
- *
- * TODO: remove this method and make CREDENTIAL_TYPE distinguish between PIN and password, so
- * that this quality is no longer needs to be persisted.
- */
- private int computeKeyguardQuality(
- @CredentialType int credentialType, int requestedQuality, int passwordQuality) {
- return credentialType == CREDENTIAL_TYPE_PASSWORD
- ? Math.max(passwordQuality, requestedQuality) : passwordQuality;
- }
-
- /**
* Update device encryption password if calling user is USER_SYSTEM and device supports
* encryption.
*/
- private void updateEncryptionPasswordIfNeeded(byte[] password, int quality, int userHandle) {
+ private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) {
// Update the device encryption password.
- if (userHandle == UserHandle.USER_SYSTEM
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- if (!shouldEncryptWithCredentials(true)) {
- clearEncryptionPassword();
- } else {
- boolean numeric = quality == PASSWORD_QUALITY_NUMERIC;
- boolean numericComplex = quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
- int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
- : StorageManager.CRYPT_TYPE_PASSWORD;
- updateEncryptionPassword(type, password);
- }
+ if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) {
+ return;
}
+ if (!shouldEncryptWithCredentials(true)) {
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
+ return;
+ }
+ if (credential.isNone()) {
+ // Set the encryption password to default.
+ setCredentialRequiredToDecrypt(false);
+ }
+ updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential());
}
/**
* Store the hash of the *current* password in the password history list, if device policy
* enforces password history requirement.
*/
- private void updatePasswordHistory(byte[] password, int userHandle) {
- if (password == null || password.length == 0) {
- Log.e(TAG, "checkPasswordHistory: empty password");
+ private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
+ if (password.isNone()) {
+ return;
+ }
+ if (password.isPattern()) {
+ // Do not keep track of historical patterns
return;
}
// Add the password to the password history. We assume all
@@ -1085,10 +853,10 @@
passwordHistory = "";
} else {
final byte[] hashFactor = getPasswordHistoryHashFactor(password, userHandle);
- String hash = passwordToHistoryHash(password, hashFactor, userHandle);
+ String hash = passwordToHistoryHash(password.getCredential(), hashFactor, userHandle);
if (hash == null) {
Log.e(TAG, "Compute new style password hash failed, fallback to legacy style");
- hash = legacyPasswordToHash(password, userHandle);
+ hash = legacyPasswordToHash(password.getCredential(), userHandle);
}
if (TextUtils.isEmpty(passwordHistory)) {
passwordHistory = hash;
@@ -1132,8 +900,8 @@
}
/**
- * Retrieves the quality mode for {@param userHandle}.
- * {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ * Retrieves the quality mode for {@code userHandle}.
+ * @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)
*
* @return stored password quality
*/
@@ -1147,37 +915,37 @@
}
/**
- * Enables/disables the Separate Profile Challenge for this {@param userHandle}. This is a no-op
+ * Enables/disables the Separate Profile Challenge for this {@code userHandle}. This is a no-op
* for user handles that do not belong to a managed profile.
*
* @param userHandle Managed profile user id
* @param enabled True if separate challenge is enabled
- * @param managedUserPassword Managed profile previous password. Null when {@param enabled} is
+ * @param managedUserPassword Managed profile previous password. Null when {@code enabled} is
* true
*/
public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
- byte[] managedUserPassword) {
+ LockscreenCredential managedUserPassword) {
if (!isManagedProfile(userHandle)) {
return;
}
try {
getLockSettings().setSeparateProfileChallengeEnabled(userHandle, enabled,
- managedUserPassword);
- onAfterChangingPassword(userHandle);
+ managedUserPassword.getCredential());
+ reportEnabledTrustAgentsChanged(userHandle);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't update work profile challenge enabled");
}
}
/**
- * Returns true if {@param userHandle} is a managed profile with separate challenge.
+ * Returns true if {@code userHandle} is a managed profile with separate challenge.
*/
public boolean isSeparateProfileChallengeEnabled(int userHandle) {
return isManagedProfile(userHandle) && hasSeparateChallenge(userHandle);
}
/**
- * Returns true if {@param userHandle} is a managed profile with unified challenge.
+ * Returns true if {@code userHandle} is a managed profile with unified challenge.
*/
public boolean isManagedProfileWithUnifiedChallenge(int userHandle) {
return isManagedProfile(userHandle) && !hasSeparateChallenge(userHandle);
@@ -1218,20 +986,6 @@
/**
* Deserialize a pattern.
- * @param string The pattern serialized with {@link #patternToString}
- * @return The pattern.
- * @deprecated Pass patterns as byte[] and use byteArrayToPattern
- */
- @Deprecated
- public static List<LockPatternView.Cell> stringToPattern(String string) {
- if (string == null) {
- return null;
- }
- return byteArrayToPattern(string.getBytes());
- }
-
- /**
- * Deserialize a pattern.
* @param bytes The pattern serialized with {@link #patternToByteArray}
* @return The pattern.
*/
@@ -1252,19 +1006,6 @@
/**
* Serialize a pattern.
* @param pattern The pattern.
- * @return The pattern in string form.
- * @deprecated Use patternToByteArray instead.
- */
- @UnsupportedAppUsage
- @Deprecated
- public static String patternToString(List<LockPatternView.Cell> pattern) {
- return new String(patternToByteArray(pattern));
- }
-
-
- /**
- * Serialize a pattern.
- * @param pattern The pattern.
* @return The pattern in byte array form.
*/
public static byte[] patternToByteArray(List<LockPatternView.Cell> pattern) {
@@ -1281,34 +1022,6 @@
return res;
}
- /*
- * Generate an SHA-1 hash for the pattern. Not the most secure, but it is
- * at least a second level of protection. First level is that the file
- * is in a location only readable by the system process.
- * @param pattern the gesture pattern.
- * @return the hash of the pattern in a byte array.
- */
- @UnsupportedAppUsage
- public static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
- if (pattern == null) {
- return null;
- }
-
- final int patternSize = pattern.size();
- byte[] res = new byte[patternSize];
- for (int i = 0; i < patternSize; i++) {
- LockPatternView.Cell cell = pattern.get(i);
- res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
- }
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- byte[] hash = md.digest(res);
- return hash;
- } catch (NoSuchAlgorithmException nsa) {
- return res;
- }
- }
-
private String getSalt(int userId) {
long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0, userId);
if (salt == 0) {
@@ -1332,6 +1045,7 @@
* @param password the gesture pattern.
*
* @return the hash of the pattern in a byte array.
+ * TODO: move to LockscreenCredential class
*/
public String legacyPasswordToHash(byte[] password, int userId) {
if (password == null || password.length == 0) {
@@ -1362,6 +1076,7 @@
/**
* Hash the password for password history check purpose.
+ * TODO: move to LockscreenCredential class
*/
private String passwordToHistoryHash(byte[] passwordToHash, byte[] hashFactor, int userId) {
if (passwordToHash == null || passwordToHash.length == 0 || hashFactor == null) {
@@ -1629,7 +1344,7 @@
}
/**
- * Disable trust until credentials have been entered for user {@param userId}.
+ * Disable trust until credentials have been entered for user {@code userId}.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
*
@@ -1640,7 +1355,7 @@
}
/**
- * Requests strong authentication for user {@param userId}.
+ * Requests strong authentication for user {@code userId}.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
*
@@ -1657,7 +1372,7 @@
}
}
- private void onAfterChangingPassword(int userHandle) {
+ private void reportEnabledTrustAgentsChanged(int userHandle) {
getTrustManager().reportEnabledTrustAgentsChanged(userHandle);
}
@@ -1823,54 +1538,36 @@
* <p>This method is only available to code running in the system server process itself.
*
* @param credential The new credential to be set
- * @param type Credential type: password / pattern / none.
- * @param requestedQuality the requested password quality by DevicePolicyManager.
- * See {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
* @param tokenHandle Handle of the escrow token
* @param token Escrow token
- * @param userId The user who's lock credential to be changed
+ * @param userHandle The user who's lock credential to be changed
* @return {@code true} if the operation is successful.
*/
- public boolean setLockCredentialWithToken(byte[] credential, int type, int requestedQuality,
- long tokenHandle, byte[] token, int userId) {
+ public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
+ byte[] token, int userHandle) {
if (!hasSecureLockScreen()) {
throw new UnsupportedOperationException(
"This operation requires the lock screen feature.");
}
+ credential.checkLength();
LockSettingsInternal localService = getLockSettingsInternal();
- if (type != CREDENTIAL_TYPE_NONE) {
- if (credential == null || credential.length < MIN_LOCK_PASSWORD_SIZE) {
- throw new IllegalArgumentException("password must not be null and at least "
- + "of length " + MIN_LOCK_PASSWORD_SIZE);
- }
- final int quality = PasswordMetrics.computeForCredential(type, credential).quality;
- final int keyguardQuality = computeKeyguardQuality(type, quality, requestedQuality);
- if (!localService.setLockCredentialWithToken(credential, type, tokenHandle, token,
- keyguardQuality, userId)) {
+
+ final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
+ setKeyguardStoredPasswordQuality(credential.getQuality(), userHandle);
+
+ try {
+ if (!localService.setLockCredentialWithToken(credential.getCredential(),
+ credential.getType(),
+ tokenHandle, token, credential.getType(), userHandle)) {
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
return false;
}
- setKeyguardStoredPasswordQuality(quality, userId);
-
- updateEncryptionPasswordIfNeeded(credential, quality, userId);
- updatePasswordHistory(credential, userId);
- onAfterChangingPassword(userId);
- } else {
- if (!(credential == null || credential.length == 0)) {
- throw new IllegalArgumentException("password must be emtpy for NONE type");
- }
- if (!localService.setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, tokenHandle,
- token, PASSWORD_QUALITY_UNSPECIFIED, userId)) {
- return false;
- }
- setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userId);
-
- if (userId == UserHandle.USER_SYSTEM) {
- // Set the encryption password to default.
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- setCredentialRequiredToDecrypt(false);
- }
+ } catch (RuntimeException e) {
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+ throw new RuntimeException("Unable to save lock credential", e);
}
- onAfterChangingPassword(userId);
+
+ onPostPasswordChanged(credential, userHandle);
return true;
}
@@ -1997,7 +1694,7 @@
}
/**
- * @return true if unlocking with trust alone is allowed for {@param userId} by the current
+ * @return true if unlocking with trust alone is allowed for {@code userId} by the current
* strong authentication requirements.
*/
public boolean isTrustAllowedForUser(int userId) {
@@ -2005,7 +1702,7 @@
}
/**
- * @return true if unlocking with a biometric method alone is allowed for {@param userId}
+ * @return true if unlocking with a biometric method alone is allowed for {@code userId}
* by the current strong authentication requirements.
*/
public boolean isBiometricAllowedForUser(int userId) {
@@ -2013,7 +1710,7 @@
}
/**
- * Called when the strong authentication requirements for {@param userId} changed.
+ * Called when the strong authentication requirements for {@code userId} changed.
*/
public void onStrongAuthRequiredChanged(int userId) {
}
@@ -2102,22 +1799,4 @@
return FRP_CREDENTIAL_ENABLED && context.getResources().getBoolean(
com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
}
-
- /**
- * Converts a CharSequence to a byte array without requiring a toString(), which creates an
- * additional copy.
- *
- * @param chars The CharSequence to convert
- * @return A byte array representing the input
- */
- public static byte[] charSequenceToByteArray(CharSequence chars) {
- if (chars == null) {
- return null;
- }
- byte[] bytes = new byte[chars.length()];
- for (int i = 0; i < chars.length(); i++) {
- bytes[i] = (byte) chars.charAt(i);
- }
- return bytes;
- }
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 3f6c4d4..74a0aa3 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -1318,7 +1318,7 @@
super.onRestoreInstanceState(ss.getSuperState());
setPattern(
DisplayMode.Correct,
- LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
+ LockPatternUtils.byteArrayToPattern(ss.getSerializedPattern().getBytes()));
mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
mInputEnabled = ss.isInputEnabled();
mInStealthMode = ss.isInStealthMode();
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
new file mode 100644
index 0000000..19e6d97
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing a lockscreen credential. It can be either an empty password, a pattern
+ * or a password (or PIN).
+ *
+ * <p> As required by some security certification, the framework tries its best to
+ * remove copies of the lockscreen credential bytes from memory. In this regard, this class
+ * abuses the {@link AutoCloseable} interface for sanitizing memory. This
+ * presents a nice syntax to auto-zeroize memory with the try-with-resource statement:
+ * <pre>
+ * try {LockscreenCredential credential = LockscreenCredential.createPassword(...) {
+ * // Process the credential in some way
+ * }
+ * </pre>
+ * With this construct, we can garantee that there will be no copies of the password left in
+ * memory when the credential goes out of scope. This should help mitigate certain class of
+ * attacks where the attcker gains read-only access to full device memory (cold boot attack,
+ * unsecured software/hardware memory dumping interfaces such as JTAG).
+ */
+public class LockscreenCredential implements Parcelable, AutoCloseable {
+
+ private final int mType;
+ // Stores raw credential bytes, or null if credential has been zeroized. An empty password
+ // is represented as a byte array of length 0.
+ private byte[] mCredential;
+ // Store the quality of the password, this is used to distinguish between pin
+ // (PASSWORD_QUALITY_NUMERIC) and password (PASSWORD_QUALITY_ALPHABETIC).
+ private final int mQuality;
+
+ /**
+ * Private constructor, use static builder methods instead.
+ *
+ * <p> Builder methods should create a private copy of the credential bytes and pass in here.
+ * LockscreenCredential will only store the reference internally without copying. This is to
+ * minimize the number of extra copies introduced.
+ */
+ private LockscreenCredential(int type, int quality, byte[] credential) {
+ Preconditions.checkNotNull(credential);
+ if (type == CREDENTIAL_TYPE_NONE) {
+ Preconditions.checkArgument(credential.length == 0);
+ } else {
+ Preconditions.checkArgument(credential.length > 0);
+ }
+ mType = type;
+ mQuality = quality;
+ mCredential = credential;
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing empty password.
+ */
+ public static LockscreenCredential createNone() {
+ return new LockscreenCredential(CREDENTIAL_TYPE_NONE, PASSWORD_QUALITY_UNSPECIFIED,
+ new byte[0]);
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given pattern.
+ */
+ public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
+ return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
+ PASSWORD_QUALITY_SOMETHING,
+ LockPatternUtils.patternToByteArray(pattern));
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given alphabetic password.
+ */
+ public static LockscreenCredential createPassword(@NonNull CharSequence password) {
+ return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+ PASSWORD_QUALITY_ALPHABETIC,
+ charSequenceToByteArray(password));
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given numeric PIN.
+ */
+ public static LockscreenCredential createPin(@NonNull CharSequence pin) {
+ return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+ PASSWORD_QUALITY_NUMERIC,
+ charSequenceToByteArray(pin));
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given alphabetic password.
+ * If the supplied password is empty, create an empty credential object.
+ */
+ public static LockscreenCredential createPasswordOrNone(@Nullable CharSequence password) {
+ if (TextUtils.isEmpty(password)) {
+ return createNone();
+ } else {
+ return createPassword(password);
+ }
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given numeric PIN.
+ * If the supplied password is empty, create an empty credential object.
+ */
+ public static LockscreenCredential createPinOrNone(@Nullable CharSequence pin) {
+ if (TextUtils.isEmpty(pin)) {
+ return createNone();
+ } else {
+ return createPin(pin);
+ }
+ }
+
+ /**
+ * Create a LockscreenCredential object based on raw credential and type
+ * TODO: Remove once LSS.setUserPasswordMetrics accepts a LockscreenCredential
+ */
+ public static LockscreenCredential createRaw(int type, byte[] credential) {
+ if (type == CREDENTIAL_TYPE_NONE) {
+ return createNone();
+ } else {
+ return new LockscreenCredential(type, PASSWORD_QUALITY_UNSPECIFIED, credential);
+ }
+ }
+
+ private void ensureNotZeroized() {
+ Preconditions.checkState(mCredential != null, "Credential is already zeroized");
+ }
+ /**
+ * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
+ * {@link #CREDENTIAL_TYPE_PATTERN} or {@link #CREDENTIAL_TYPE_PASSWORD}.
+ *
+ * TODO: Remove once credential type is internal. Callers should use {@link #isNone},
+ * {@link #isPattern} and {@link #isPassword} instead.
+ */
+ public int getType() {
+ ensureNotZeroized();
+ return mType;
+ }
+
+ /**
+ * Returns the quality type of the credential
+ */
+ public int getQuality() {
+ ensureNotZeroized();
+ return mQuality;
+ }
+
+ /**
+ * Returns the credential bytes. This is a direct reference of the internal field so
+ * callers should not modify it.
+ *
+ */
+ public byte[] getCredential() {
+ ensureNotZeroized();
+ return mCredential;
+ }
+
+ /**
+ * Returns the credential type recognized by {@link StorageManager}. Can be one of
+ * {@link StorageManager#CRYPT_TYPE_DEFAULT}, {@link StorageManager#CRYPT_TYPE_PATTERN},
+ * {@link StorageManager#CRYPT_TYPE_PIN} or {@link StorageManager#CRYPT_TYPE_PASSWORD}.
+ */
+ public int getStorageCryptType() {
+ if (isNone()) {
+ return StorageManager.CRYPT_TYPE_DEFAULT;
+ }
+ if (isPattern()) {
+ return StorageManager.CRYPT_TYPE_PATTERN;
+ }
+ if (isPassword()) {
+ return mQuality == PASSWORD_QUALITY_NUMERIC
+ ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
+ }
+ throw new IllegalStateException("Unhandled credential type");
+ }
+
+ /** Returns whether this is an empty credential */
+ public boolean isNone() {
+ ensureNotZeroized();
+ return mType == CREDENTIAL_TYPE_NONE;
+ }
+
+ /** Returns whether this is a pattern credential */
+ public boolean isPattern() {
+ ensureNotZeroized();
+ return mType == CREDENTIAL_TYPE_PATTERN;
+ }
+
+ /** Returns whether this is a password credential */
+ public boolean isPassword() {
+ ensureNotZeroized();
+ return mType == CREDENTIAL_TYPE_PASSWORD;
+ }
+
+ /** Returns the length of the credential */
+ public int size() {
+ ensureNotZeroized();
+ return mCredential.length;
+ }
+
+ /** Create a copy of the credential */
+ public LockscreenCredential duplicate() {
+ return new LockscreenCredential(mType, mQuality,
+ mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
+ }
+
+ /**
+ * Zeroize the credential bytes.
+ */
+ public void zeroize() {
+ if (mCredential != null) {
+ Arrays.fill(mCredential, (byte) 0);
+ mCredential = null;
+ }
+ }
+
+ /**
+ * Check if the credential meets minimal length requirement.
+ *
+ * @throws IllegalArgumentException if the credential is too short.
+ */
+ public void checkLength() {
+ if (isNone()) {
+ return;
+ }
+ if (isPattern()) {
+ if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+ throw new IllegalArgumentException("pattern must not be null and at least "
+ + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
+ }
+ return;
+ }
+ if (isPassword()) {
+ if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
+ throw new IllegalArgumentException("password must not be null and at least "
+ + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
+ }
+ return;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mQuality);
+ dest.writeByteArray(mCredential);
+ }
+
+ public static final Parcelable.Creator<LockscreenCredential> CREATOR =
+ new Parcelable.Creator<LockscreenCredential>() {
+
+ @Override
+ public LockscreenCredential createFromParcel(Parcel source) {
+ return new LockscreenCredential(source.readInt(), source.readInt(),
+ source.createByteArray());
+ }
+
+ @Override
+ public LockscreenCredential[] newArray(int size) {
+ return new LockscreenCredential[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void close() {
+ zeroize();
+ }
+
+ @Override
+ public int hashCode() {
+ // Effective Java — Item 9
+ return ((17 + mType) * 31 + mQuality) * 31 + mCredential.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof LockscreenCredential)) return false;
+ final LockscreenCredential other = (LockscreenCredential) o;
+ return mType == other.mType && mQuality == other.mQuality
+ && Arrays.equals(mCredential, other.mCredential);
+ }
+
+ /**
+ * Converts a CharSequence to a byte array without requiring a toString(), which creates an
+ * additional copy.
+ *
+ * @param chars The CharSequence to convert
+ * @return A byte array representing the input
+ */
+ private static byte[] charSequenceToByteArray(CharSequence chars) {
+ if (chars == null) {
+ return new byte[0];
+ }
+ byte[] bytes = new byte[chars.length()];
+ for (int i = 0; i < chars.length(); i++) {
+ bytes[i] = (byte) chars.charAt(i);
+ }
+ return bytes;
+ }
+}
diff --git a/core/java/com/android/internal/widget/PasswordValidationError.java b/core/java/com/android/internal/widget/PasswordValidationError.java
new file mode 100644
index 0000000..41b234e
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordValidationError.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * Password validation error containing an error code and optional requirement.
+ */
+public class PasswordValidationError {
+ // Password validation error codes
+ public static final int WEAK_CREDENTIAL_TYPE = 1;
+ public static final int CONTAINS_INVALID_CHARACTERS = 2;
+ public static final int TOO_SHORT = 3;
+ public static final int TOO_LONG = 4;
+ public static final int CONTAINS_SEQUENCE = 5;
+ public static final int NOT_ENOUGH_LETTERS = 6;
+ public static final int NOT_ENOUGH_UPPER_CASE = 7;
+ public static final int NOT_ENOUGH_LOWER_CASE = 8;
+ public static final int NOT_ENOUGH_DIGITS = 9;
+ public static final int NOT_ENOUGH_SYMBOLS = 10;
+ public static final int NOT_ENOUGH_NON_LETTER = 11;
+ public static final int NOT_ENOUGH_NON_DIGITS = 12;
+ public static final int RECENTLY_USED = 13;
+ // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings.
+
+ public final int errorCode;
+ public final int requirement;
+
+ public PasswordValidationError(int errorCode) {
+ this(errorCode, 0);
+ }
+
+ public PasswordValidationError(int errorCode, int requirement) {
+ this.errorCode = errorCode;
+ this.requirement = requirement;
+ }
+
+ @Override
+ public String toString() {
+ return errorCodeToString(errorCode) + (requirement > 0 ? "; required: " + requirement : "");
+ }
+
+ /**
+ * Returns textual representation of the error for logging purposes.
+ */
+ private static String errorCodeToString(int error) {
+ switch (error) {
+ case WEAK_CREDENTIAL_TYPE: return "Weak credential type";
+ case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character";
+ case TOO_SHORT: return "Password too short";
+ case TOO_LONG: return "Password too long";
+ case CONTAINS_SEQUENCE: return "Sequence too long";
+ case NOT_ENOUGH_LETTERS: return "Too few letters";
+ case NOT_ENOUGH_UPPER_CASE: return "Too few upper case letters";
+ case NOT_ENOUGH_LOWER_CASE: return "Too few lower case letters";
+ case NOT_ENOUGH_DIGITS: return "Too few numeric characters";
+ case NOT_ENOUGH_SYMBOLS: return "Too few symbols";
+ case NOT_ENOUGH_NON_LETTER: return "Too few non-letter characters";
+ case NOT_ENOUGH_NON_DIGITS: return "Too few non-numeric characters";
+ case RECENTLY_USED: return "Pin or password was recently used";
+ default: return "Unknown error " + error;
+ }
+ }
+
+}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 697825d..ea0389f 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -168,6 +168,10 @@
// These are the permitted backup transport service components
final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
+ // These are packages mapped to maps of component class name to default enabled state.
+ final ArrayMap<String, ArrayMap<String, Boolean>> mPackageComponentEnabledState =
+ new ArrayMap<>();
+
// Package names that are exempted from private API blacklisting
final ArraySet<String> mHiddenApiPackageWhitelist = new ArraySet<>();
@@ -301,6 +305,10 @@
return mBackupTransportWhitelist;
}
+ public ArrayMap<String, Boolean> getComponentsEnabledStates(String packageName) {
+ return mPackageComponentEnabledState.get(packageName);
+ }
+
public ArraySet<String> getDisabledUntilUsedPreinstalledCarrierApps() {
return mDisabledUntilUsedPreinstalledCarrierApps;
}
@@ -846,6 +854,14 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "component-override": {
+ if (allowAppConfigs) {
+ readComponentOverrides(parser, permFile);
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "backup-transport-whitelisted-service": {
if (allowFeatures) {
String serviceName = parser.getAttributeValue(null, "service");
@@ -1269,6 +1285,54 @@
}
}
+ private void readComponentOverrides(XmlPullParser parser, File permFile)
+ throws IOException, XmlPullParserException {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<component-override> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ return;
+ }
+
+ pkgname = pkgname.intern();
+
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ String name = parser.getName();
+ if ("component".equals(name)) {
+ String clsname = parser.getAttributeValue(null, "class");
+ String enabled = parser.getAttributeValue(null, "enabled");
+ if (clsname == null) {
+ Slog.w(TAG, "<component> without class in "
+ + permFile + " at " + parser.getPositionDescription());
+ return;
+ } else if (enabled == null) {
+ Slog.w(TAG, "<component> without enabled in "
+ + permFile + " at " + parser.getPositionDescription());
+ return;
+ }
+
+ if (clsname.startsWith(".")) {
+ clsname = pkgname + clsname;
+ }
+
+ clsname = clsname.intern();
+
+ ArrayMap<String, Boolean> componentEnabledStates =
+ mPackageComponentEnabledState.get(pkgname);
+ if (componentEnabledStates == null) {
+ componentEnabledStates = new ArrayMap<>();
+ mPackageComponentEnabledState.put(pkgname,
+ componentEnabledStates);
+ }
+
+ componentEnabledStates.put(clsname, !"false".equals(enabled));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
private static boolean isSystemProcess() {
return Process.myUid() == Process.SYSTEM_UID;
}
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index bd4862d..6370253 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -16,6 +16,7 @@
#define ATRACE_TAG ATRACE_TAG_RESOURCES
+#include "android-base/logging.h"
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
#include "android-base/unique_fd.h"
@@ -32,7 +33,7 @@
namespace android {
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
- jboolean force_shared_lib, jboolean overlay) {
+ jboolean force_shared_lib, jboolean overlay, jboolean for_loader) {
ScopedUtfChars path(env, java_path);
if (path.c_str() == nullptr) {
return 0;
@@ -46,7 +47,7 @@
} else if (force_shared_lib) {
apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
} else {
- apk_assets = ApkAssets::Load(path.c_str(), system);
+ apk_assets = ApkAssets::Load(path.c_str(), system, for_loader);
}
if (apk_assets == nullptr) {
@@ -58,7 +59,8 @@
}
static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
- jstring friendly_name, jboolean system, jboolean force_shared_lib) {
+ jstring friendly_name, jboolean system, jboolean force_shared_lib,
+ jboolean for_loader) {
ScopedUtfChars friendly_name_utf8(env, friendly_name);
if (friendly_name_utf8.c_str() == nullptr) {
return 0;
@@ -80,7 +82,9 @@
std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
friendly_name_utf8.c_str(),
- system, force_shared_lib);
+ system, force_shared_lib,
+ for_loader);
+
if (apk_assets == nullptr) {
std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
friendly_name_utf8.c_str(), dup_fd.get());
@@ -90,6 +94,60 @@
return reinterpret_cast<jlong>(apk_assets.release());
}
+static jlong NativeLoadArsc(JNIEnv* env, jclass /*clazz*/, jstring java_path,
+ jboolean for_loader) {
+ ScopedUtfChars path(env, java_path);
+ if (path.c_str() == nullptr) {
+ return 0;
+ }
+
+ ATRACE_NAME(base::StringPrintf("LoadApkAssetsArsc(%s)", path.c_str()).c_str());
+
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadArsc(path.c_str(), for_loader);
+
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
+ jstring friendly_name, jboolean for_loader) {
+ ScopedUtfChars friendly_name_utf8(env, friendly_name);
+ if (friendly_name_utf8.c_str() == nullptr) {
+ return 0;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ ATRACE_NAME(base::StringPrintf("LoadApkAssetsArscFd(%d)", fd).c_str());
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<const ApkAssets> apk_assets =
+ ApkAssets::LoadArsc(std::move(dup_fd), friendly_name_utf8.c_str(), for_loader);
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path from fd %d", fd);
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jboolean for_loader) {
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadEmpty(for_loader);
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
delete reinterpret_cast<ApkAssets*>(ptr);
}
@@ -138,9 +196,13 @@
// JNI registration.
static const JNINativeMethod gApkAssetsMethods[] = {
- {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
- {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
+ {"nativeLoad", "(Ljava/lang/String;ZZZZ)J", (void*)NativeLoad},
+ {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZ)J",
(void*)NativeLoadFromFd},
+ {"nativeLoadArsc", "(Ljava/lang/String;Z)J", (void*)NativeLoadArsc},
+ {"nativeLoadArscFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)J",
+ (void*)NativeLoadArscFromFd},
+ {"nativeLoadEmpty", "(Z)J", (void*)NativeLoadEmpty},
{"nativeDestroy", "(J)V", (void*)NativeDestroy},
{"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 9445319..d62d2d9 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -139,8 +139,8 @@
"nativePrivateClean", "nativeSharedClean", "nativeSwappedOut", "nativeSwappedOutPss" }
};
-jfieldID otherStats_field;
-jfieldID hasSwappedOutPss_field;
+static jfieldID otherStats_field;
+static jfieldID hasSwappedOutPss_field;
struct stats_t {
int pss;
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index daf33f6..c7b36d0 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -108,7 +108,7 @@
jmethodID put;
} gArrayMapOffsets;
-jclass g_stringClass = nullptr;
+static jclass g_stringClass = nullptr;
// ----------------------------------------------------------------------------
@@ -763,6 +763,41 @@
return reinterpret_cast<jlong>(xml_tree.release());
}
+static jlong NativeOpenXmlAssetFd(JNIEnv* env, jobject /*clazz*/, jlong ptr, int jcookie,
+ jobject file_descriptor) {
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ ATRACE_NAME(base::StringPrintf("AssetManager::OpenXmlAssetFd(%d)", fd).c_str());
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ base::unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<Asset>
+ asset(Asset::createFromFd(dup_fd.release(), nullptr, Asset::AccessMode::ACCESS_BUFFER));
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+
+ // May be nullptr.
+ const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
+
+ std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
+ status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+ asset.reset();
+
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+ return 0;
+ }
+ return reinterpret_cast<jlong>(xml_tree.release());
+}
+
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) {
@@ -1564,6 +1599,7 @@
{"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
(void*)NativeOpenNonAssetFd},
{"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+ {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
// AssetManager resource methods.
{"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index e406e22..2232393 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -992,6 +992,31 @@
return IPCThreadState::self()->blockUntilThreadAvailable();
}
+static jobject android_os_Binder_waitForService(
+ JNIEnv *env,
+ jclass /* clazzObj */,
+ jstring serviceNameObj) {
+
+ const jchar* serviceName = env->GetStringCritical(serviceNameObj, nullptr);
+ if (!serviceName) {
+ signalExceptionForError(env, nullptr, BAD_VALUE, true /*canThrowRemoteException*/);
+ return nullptr;
+ }
+ String16 nameCopy = String16(reinterpret_cast<const char16_t *>(serviceName),
+ env->GetStringLength(serviceNameObj));
+ env->ReleaseStringCritical(serviceNameObj, serviceName);
+
+ auto sm = android::defaultServiceManager();
+ sp<IBinder> service = sm->waitForService(nameCopy);
+
+ if (!service) {
+ signalExceptionForError(env, nullptr, NAME_NOT_FOUND, true /*canThrowRemoteException*/);
+ return nullptr;
+ }
+
+ return javaObjectForIBinder(env, service);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gBinderMethods[] = {
@@ -1019,7 +1044,8 @@
{ "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
{ "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
{ "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
- { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
+ { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
+ { "waitForService", "(Ljava/lang/String;)Landroid/os/IBinder;", (void*)android_os_Binder_waitForService }
};
const char* const kBinderPathName = "android/os/Binder";
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 0fada1b..49c5cad 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -35,7 +35,6 @@
#include <limits>
#include <memory>
#include <string>
-#include <unordered_map>
#include <vector>
#include "core_jni_helpers.h"
@@ -62,45 +61,26 @@
using namespace android;
-static const bool kDebugPolicy = false;
-static const bool kDebugProc = false;
+static constexpr bool kDebugPolicy = false;
+static constexpr bool kDebugProc = false;
// Stack reservation for reading small proc files. Most callers of
// readProcFile() are reading files under this threshold, e.g.,
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
// bytes, so use 1024 for the stack to provide a bit of slack.
-static const ssize_t kProcReadStackBufferSize = 1024;
+static constexpr ssize_t kProcReadStackBufferSize = 1024;
// The other files we read from proc tend to be a bit larger (e.g.,
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
// retry with a relatively large heap-allocated buffer. We double
// this size and retry until the whole file fits.
-static const ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
#if GUARD_THREAD_PRIORITY
Mutex gKeyCreateMutex;
static pthread_key_t gBgKey = -1;
#endif
-/*
- * cpuset/sched aggregate profile mappings
- */
-static const std::unordered_map<int, std::string> kCpusetProfileMap = {
- {SP_DEFAULT, "CPUSET_SP_DEFAULT"}, {SP_BACKGROUND, "CPUSET_SP_BACKGROUND"},
- {SP_FOREGROUND, "CPUSET_SP_FOREGROUND"},{SP_SYSTEM, "CPUSET_SP_SYSTEM"},
- {SP_AUDIO_APP, "CPUSET_SP_FOREGROUND"}, {SP_AUDIO_SYS, "CPUSET_SP_FOREGROUND"},
- {SP_TOP_APP, "CPUSET_SP_TOP_APP"}, {SP_RT_APP, "CPUSET_SP_DEFAULT"},
- {SP_RESTRICTED, "CPUSET_SP_RESTRICTED"}
-};
-
-static const std::unordered_map<int, std::string> kSchedProfileMap = {
- {SP_DEFAULT, "SCHED_SP_DEFAULT"}, {SP_BACKGROUND, "SCHED_SP_BACKGROUND"},
- {SP_FOREGROUND, "SCHED_SP_FOREGROUND"}, {SP_SYSTEM, "SCHED_SP_DEFAULT"},
- {SP_AUDIO_APP, "SCHED_SP_FOREGROUND"}, {SP_AUDIO_SYS, "SCHED_SP_FOREGROUND"},
- {SP_TOP_APP, "SCHED_SP_TOP_APP"}, {SP_RT_APP, "SCHED_SP_RT_APP"},
- {SP_RESTRICTED, "SCHED_SP_DEFAULT"}
-};
-
// For both of these, err should be in the errno range (positive), not a status_t (negative)
static void signalExceptionForError(JNIEnv* env, int err, int tid) {
switch (err) {
@@ -227,7 +207,7 @@
return;
}
- int res = SetTaskProfiles(tid, {kSchedProfileMap.at(grp)}, true) ? 0 : -1;
+ int res = SetTaskProfiles(tid, {get_sched_policy_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
@@ -241,7 +221,7 @@
return;
}
- int res = SetTaskProfiles(tid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ int res = SetTaskProfiles(tid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
@@ -328,7 +308,7 @@
if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
// This task wants to stay at background
// update its cpuset so it doesn't only run on bg core(s)
- err = SetTaskProfiles(t_pid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ err = SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (err != NO_ERROR) {
signalExceptionForGroupError(env, -err, t_pid);
break;
@@ -337,7 +317,7 @@
}
}
- err = SetTaskProfiles(t_pid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ err = SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (err != NO_ERROR) {
signalExceptionForGroupError(env, -err, t_pid);
break;
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index af34e7b7..bf1cea8 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -112,7 +112,9 @@
}
static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
- std::unique_ptr<NativeInputChannel> nativeInputChannel) {
+ sp<InputChannel> inputChannel) {
+ std::unique_ptr<NativeInputChannel> nativeInputChannel =
+ std::make_unique<NativeInputChannel>(inputChannel);
jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
gInputChannelClassInfo.ctor);
if (inputChannelObj) {
@@ -143,14 +145,12 @@
return nullptr;
}
- jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
- std::make_unique<NativeInputChannel>(serverChannel));
+ jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, serverChannel);
if (env->ExceptionCheck()) {
return nullptr;
}
- jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
- std::make_unique<NativeInputChannel>(clientChannel));
+ jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, clientChannel);
if (env->ExceptionCheck()) {
return nullptr;
}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b8c5270..94be61f 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2412,4 +2412,18 @@
// OS: R
SETTINGS_WIFI_CONFIGURE_NETWORK = 1800;
+ // OPEN: Settings > Accessibility > Magnification
+ // CATEGORY: SETTINGS
+ // OS: R
+ // Note: Shows up only when Magnify with shortcut is enabled
+ // and under accessibility button mode.
+ DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON = 1801;
+
+ // OPEN: Settings > Accessibility > Magnification
+ // CATEGORY: SETTINGS
+ // OS: R
+ // Note: Shows up only when Magnify with shortcut is enabled.
+ // and under gesture navigation mode.
+ DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION = 1802;
+
}
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 301fa13..d010c8f 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -114,6 +114,13 @@
optional int32 distraction_flags = 10;
}
+ message InstallSourceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The package that requested the installation of this one.
+ optional string initiating_package_name = 1;
+ }
+
// Name of package. e.g. "com.android.providers.telephony".
optional string name = 1;
// UID for this package as assigned by Android OS.
@@ -133,4 +140,6 @@
repeated SplitProto splits = 8;
// Per-user package info.
repeated UserInfoProto users = 9;
+ // Where the request to install this package came from,
+ optional InstallSourceProto install_source = 10;
}
diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto
index f49a044..ad7299d 100644
--- a/core/proto/android/service/procstats.proto
+++ b/core/proto/android/service/procstats.proto
@@ -241,12 +241,25 @@
repeated StateStats active_state_stats = 6;
}
-// Next Tag: 3
+// Next Tag: 7
message PackageAssociationProcessStatsProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
// Name of the target component.
optional string component_name = 1;
+
+ // Total count of the times this association appeared.
+ optional int32 total_count = 3;
+
+ // Millisecond uptime total duration this association was around.
+ optional int64 total_duration_ms = 4;
+
+ // Total count of the times this association became actively impacting its target process.
+ optional int32 active_count = 5;
+
+ // Millisecond uptime total duration this association was around.
+ optional int64 active_duration_ms = 6;
+
// Information on one source in this association.
repeated PackageAssociationSourceProcessStatsProto sources = 2;
}
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 0821d14..15813a1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -113,9 +113,9 @@
PROVISIONING_PREPROVISIONING_ACTIVITY_TIME_MS = 87;
PROVISIONING_ENCRYPT_DEVICE_ACTIVITY_TIME_MS = 88;
PROVISIONING_WEB_ACTIVITY_TIME_MS = 89;
- PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 90;
- PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 91;
- PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 92;
+ PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 90 [deprecated=true];
+ PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 91 [deprecated=true];
+ PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 92 [deprecated=true];
PROVISIONING_NETWORK_TYPE = 93;
PROVISIONING_ACTION = 94;
PROVISIONING_EXTRAS = 95;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b030b33..f98ea2c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1624,7 +1624,7 @@
@hide This should only be used by Settings and SystemUI.
-->
<permission android:name="android.permission.NETWORK_SETTINGS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows SetupWizard to call methods in Networking services
<p>Not for use by any other third-party or privileged applications.
@@ -2138,12 +2138,12 @@
<!-- Must be required by a telephony data service to ensure that only the
system can bind to it.
- <p>Protection level: signature
+ <p>Protection level: signature|telephony
@SystemApi
@hide
-->
<permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Must be required by a NetworkService to ensure that only the
system can bind to it.
@@ -2164,11 +2164,11 @@
<!-- @SystemApi Must be required by an EuiccService to ensure that only the system can bind to
it.
- <p>Protection level: signature
+ <p>Protection level: signature|telephony
@hide
-->
<permission android:name="android.permission.BIND_EUICC_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
@@ -2955,7 +2955,7 @@
@hide
-->
<permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony|wifi" />
<!-- @SystemApi Allows an application to use
{@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
@@ -3511,6 +3511,13 @@
<permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows the system to restore runtime permission state. This might grant
+ permissions, hence this is a more scoped, less powerful variant of GRANT_RUNTIME_PERMISSIONS.
+ Among other restrictions this cannot override user choices.
+ @hide -->
+ <permission android:name="android.permission.RESTORE_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to change policy_fixed permissions.
@hide -->
<permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
@@ -3740,7 +3747,7 @@
@hide
-->
<permission android:name="android.permission.DEVICE_POWER"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows toggling battery saver on the system.
Superseded by DEVICE_POWER permission. @hide @SystemApi
@@ -3775,13 +3782,13 @@
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.BROADCAST_SMS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows an application to broadcast a WAP PUSH receipt notification.
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.BROADCAST_WAP_PUSH"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- @SystemApi Allows an application to broadcast privileged networking requests.
<p>Not for use by third-party applications.
@@ -4396,13 +4403,13 @@
{@link android.provider.BlockedNumberContract}.
@hide -->
<permission android:name="android.permission.READ_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows the holder to write blocked numbers. See
{@link android.provider.BlockedNumberContract}.
@hide -->
<permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only
the system can bind to it.
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 0bdb25a..4857095 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -22,8 +22,6 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?attr/listPreferredItemHeightSmall"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
android:background="?attr/activatedBackgroundIndicator">
<!-- Activity icon when presenting dialog
@@ -32,7 +30,8 @@
android:layout_width="@dimen/resolver_icon_size"
android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|center_vertical"
- android:layout_marginStart="?attr/listPreferredItemPaddingStart"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:layout_marginEnd="@dimen/resolver_icon_margin"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:scaleType="fitCenter" />
@@ -40,8 +39,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="start|center_vertical"
android:orientation="vertical"
- android:paddingStart="?attr/listPreferredItemPaddingStart"
- android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+ android:paddingEnd="@dimen/resolver_edge_margin"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="start|center_vertical">
@@ -49,14 +47,20 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/textAppearanceMedium"
- android:textColor="?attr/textColorPrimary"
+ android:layout_gravity="start|center_vertical"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:textSize="16sp"
android:minLines="1"
android:maxLines="1"
android:ellipsize="marquee" />
<!-- Extended activity info to distinguish between duplicate activity names -->
<TextView android:id="@android:id/text2"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:layout_gravity="start|center_vertical"
+ android:textSize="14sp"
+ android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minLines="1"
diff --git a/core/res/res/layout/resolver_different_item_header.xml b/core/res/res/layout/resolver_different_item_header.xml
index 7d9ffd7..0a35edc 100644
--- a/core/res/res/layout/resolver_different_item_header.xml
+++ b/core/res/res/layout/resolver_different_item_header.xml
@@ -22,12 +22,12 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:text="@string/use_a_different_app"
- android:minHeight="56dp"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:elevation="8dp"
- />
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:elevation="1dp" />
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 1dd4207..6e45e7a 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -29,16 +29,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
- android:elevation="8dp"
- android:background="?attr/colorBackgroundFloating">
+ android:elevation="@dimen/resolver_elevation"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:background="@drawable/bottomsheet_background">
<TextView
android:id="@+id/profile_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
android:visibility="gone"
style="?attr/borderlessButtonStyle"
android:textAppearance="?attr/textAppearanceButton"
@@ -50,36 +52,49 @@
<TextView
android:id="@+id/title"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="56dp"
- android:textAppearance="?attr/textAppearanceMedium"
- android:gravity="start|center_vertical"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
- android:paddingTop="8dp"
android:layout_below="@id/profile_button"
android:layout_alignParentStart="true"
- android:paddingBottom="8dp" />
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
+ android:gravity="start|center_vertical" />
</RelativeLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
+ android:scrollbarStyle="outsideOverlay"
android:scrollIndicators="top|bottom"
- android:divider="@null" />
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
+
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:layout_alwaysShow="true"
android:text="@string/noApplications"
android:padding="32dp"
@@ -102,18 +117,19 @@
android:background="?attr/colorBackgroundFloating"
android:paddingTop="@dimen/resolver_button_bar_spacing"
android:paddingBottom="@dimen/resolver_button_bar_spacing"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
+ android:textAllCaps="false"
android:enabled="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -123,8 +139,9 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textAllCaps="false"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/activity_resolver_use_always"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 740a7eb..dbba0b7 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -29,22 +29,22 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp">
+ android:background="@drawable/bottomsheet_background"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:orientation="horizontal">
-
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin">
<ImageView
android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/resolver_icon_size"
+ android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|top"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
android:src="@drawable/resolver_icon_placeholder"
android:scaleType="fitCenter" />
@@ -52,9 +52,11 @@
android:id="@+id/title"
android:layout_width="0dp"
android:layout_weight="1"
- android:layout_height="?attr/listPreferredItemHeight"
- android:layout_marginStart="16dp"
- android:textAppearance="?attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
android:paddingEnd="16dp" />
@@ -107,21 +109,22 @@
android:orientation="horizontal"
android:layoutDirection="locale"
android:measureWithLargestChild="true"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingTop="@dimen/resolver_button_bar_spacing"
+ android:paddingBottom="@dimen/resolver_button_bar_spacing"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -130,29 +133,40 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_always"
android:onClick="onButtonClick" />
</LinearLayout>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/dividerVertical" />
</LinearLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
- android:divider="@null" />
-
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollIndicators="top|bottom"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index a301702..ffcfe43 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -295,6 +295,12 @@
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system app predictor -->
<flag name="appPredictor" value="0x200000" />
+ <!-- Additional flag from base permission type: this permission can be automatically
+ granted to the system telephony apps -->
+ <flag name="telephony" value="0x400000" />
+ <!-- Additional flag from base permission type: this permission can be automatically
+ granted to the system wifi app-->
+ <flag name="wifi" value="0x800000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3fef7a2d..9bff88a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3687,6 +3687,21 @@
-->
<string name="config_defaultWellbeingPackage" translatable="false"></string>
+ <!-- The package name for the system telephony apps.
+ This package must be trusted, as it will be granted with permissions with special telephony
+ protection level. Note, framework by default support multiple telephony apps, each package
+ name is separated by comma.
+ Example: "com.android.phone,com.android.stk,com.android.providers.telephony"
+ -->
+ <string name="config_telephonyPackages" translatable="false">"com.android.phone,com.android.stk,com.android.providers.telephony,com.android.ons"</string>
+
+ <!-- The package name for the default system wifi app.
+ This package must be trusted, as it has the permissions to control wifi
+ connectivity on the device.
+ Example: "com.android.wifi"
+ -->
+ <string name="config_wifiPackage" translatable="false">"com.android.wifi"</string>
+
<!-- The component name for the default system attention service.
This service must be trusted, as it can be activated without explicit consent of the user.
See android.attention.AttentionManagerService.
@@ -4304,11 +4319,11 @@
<!-- Trigger a warning for notifications with RemoteView objects that are larger in bytes than
this value (default 1MB)-->
- <integer name="config_notificationWarnRemoteViewSizeBytes">1000000</integer>
+ <integer name="config_notificationWarnRemoteViewSizeBytes">2000000</integer>
<!-- Strip notification RemoteView objects that are larger in bytes than this value (also log)
(default 2MB) -->
- <integer name="config_notificationStripRemoteViewSizeBytes">2000000</integer>
+ <integer name="config_notificationStripRemoteViewSizeBytes">5000000</integer>
<!-- Contains a blacklist of apps that should not get pre-installed carrier app permission
grants, even if the UICC claims that the app should be privileged. See b/138150105 -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 609659b..a01bbe3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -750,7 +750,7 @@
<dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen>
- <!-- chooser (sharesheet) spacing -->
+ <!-- chooser/resolver (sharesheet) spacing -->
<dimen name="chooser_corner_radius">8dp</dimen>
<dimen name="chooser_row_text_option_translate">25dp</dimen>
<dimen name="chooser_view_spacing">18dp</dimen>
@@ -759,11 +759,15 @@
<dimen name="chooser_preview_image_font_size">20sp</dimen>
<dimen name="chooser_preview_image_border">1dp</dimen>
<dimen name="chooser_preview_width">-1px</dimen>
- <dimen name="resolver_icon_size">42dp</dimen>
- <dimen name="resolver_button_bar_spacing">8dp</dimen>
- <dimen name="resolver_badge_size">18dp</dimen>
<dimen name="chooser_target_width">90dp</dimen>
<dimen name="chooser_header_scroll_elevation">4dp</dimen>
<dimen name="chooser_max_collapsed_height">288dp</dimen>
<dimen name="chooser_direct_share_label_placeholder_max_width">72dp</dimen>
+ <dimen name="resolver_icon_size">32dp</dimen>
+ <dimen name="resolver_button_bar_spacing">8dp</dimen>
+ <dimen name="resolver_badge_size">18dp</dimen>
+ <dimen name="resolver_icon_margin">16dp</dimen>
+ <dimen name="resolver_small_margin">18dp</dimen>
+ <dimen name="resolver_edge_margin">24dp</dimen>
+ <dimen name="resolver_elevation">1dp</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 363bc9d..42cd2cd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3467,6 +3467,8 @@
<java-symbol type="string" name="config_defaultAutofillService" />
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
<java-symbol type="string" name="config_defaultWellbeingPackage" />
+ <java-symbol type="string" name="config_telephonyPackages" />
+ <java-symbol type="string" name="config_wifiPackage" />
<java-symbol type="string" name="config_defaultContentCaptureService" />
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
<java-symbol type="string" name="config_defaultAppPredictionService" />
@@ -3819,6 +3821,10 @@
<java-symbol type="dimen" name="resolver_icon_size"/>
<java-symbol type="dimen" name="resolver_badge_size"/>
<java-symbol type="dimen" name="resolver_button_bar_spacing"/>
+ <java-symbol type="dimen" name="resolver_icon_margin"/>
+ <java-symbol type="dimen" name="resolver_small_margin"/>
+ <java-symbol type="dimen" name="resolver_edge_margin"/>
+ <java-symbol type="dimen" name="resolver_elevation"/>
<!-- For DropBox -->
<java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
new file mode 100644
index 0000000..53db832
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/Android.bp
@@ -0,0 +1,63 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test {
+ name: "FrameworksResourceLoaderTests",
+ srcs: [
+ "src/**/*.kt"
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.espresso.core",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
+ ],
+ resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ],
+ test_suites: ["device-tests"],
+ sdk_version: "test_current",
+ aaptflags: [
+ "--no-compress",
+ ],
+ data: [
+ ":FrameworksResourceLoaderTestsOverlay",
+ ":FrameworksResourceLoaderTestsSplitOne",
+ ":FrameworksResourceLoaderTestsSplitTwo",
+ ],
+ java_resources: [ "NonAsset.txt" ]
+}
+
+filegroup {
+ name: "FrameworksResourceLoaderTestsResources",
+ srcs: ["resources"],
+}
+
+genrule {
+ name: "FrameworksResourceLoaderTestsAssets",
+ srcs: [
+ ":framework-res",
+ ":FrameworksResourceLoaderTestsResources",
+ ],
+ tools: [ ":aapt2", ":soong_zip" ],
+ tool_files: [ "resources/compileAndLink.sh" ],
+ cmd: "$(location resources/compileAndLink.sh) $(location :aapt2) $(location :soong_zip) $(genDir) $(in) $(in)",
+ out: [ "out.zip" ]
+}
diff --git a/core/tests/ResourceLoaderTests/AndroidManifest.xml b/core/tests/ResourceLoaderTests/AndroidManifest.xml
new file mode 100644
index 0000000..00b4ccb
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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
+ -->
+
+<!-- Split loading is tested separately, so this must be marked isolated -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ android:isolatedSplits="true"
+ >
+
+ <uses-sdk android:minSdkVersion="29"/>
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+
+ <activity
+ android:name=".TestActivity"
+ android:configChanges="orientation"
+ />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="ResourceLoaderTests"
+ android:targetPackage="android.content.res.loader.test"
+ />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml
new file mode 100644
index 0000000..702151d
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<configuration description="Test module config for ResourceLoaderTests">
+ <option name="test-tag" value="ResourceLoaderTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
+ <option name="split-apk-file-names"
+ value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk" />
+ <option name="test-file-name" value="FrameworksResourceLoaderTestsOverlay.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="cmd overlay disable android.content.res.loader.test.overlay" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.content.res.loader.test" />
+ </test>
+</configuration>
diff --git a/core/tests/ResourceLoaderTests/NonAsset.txt b/core/tests/ResourceLoaderTests/NonAsset.txt
new file mode 100644
index 0000000..5c0b2cc
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/NonAsset.txt
@@ -0,0 +1 @@
+Outside assets directory
diff --git a/core/tests/ResourceLoaderTests/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
new file mode 100644
index 0000000..897897f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "FrameworksResourceLoaderTestsSplitOne"
+}
diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
new file mode 100644
index 0000000..b14bd86
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ split="split_one"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
new file mode 100644
index 0000000..3c215eb
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="string" name="split_overlaid" id="0x7f040001" />
+ <string name="split_overlaid">Split ONE Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/assets/Asset.txt b/core/tests/ResourceLoaderTests/assets/Asset.txt
new file mode 100644
index 0000000..03f9a0f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/assets/Asset.txt
@@ -0,0 +1 @@
+In assets directory
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
new file mode 100644
index 0000000..a12e33a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
new file mode 100644
index 0000000..182cbab
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
new file mode 100644
index 0000000..e6b5f15b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
new file mode 100644
index 0000000..e9c743c
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
new file mode 100644
index 0000000..cd05360
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
new file mode 100644
index 0000000..dc8aa90
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
new file mode 100644
index 0000000..8a672ba
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
new file mode 100644
index 0000000..56f3d1e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
new file mode 100644
index 0000000..663d312
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
new file mode 100644
index 0000000..5f6e4b8
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/overlay/Android.bp
new file mode 100644
index 0000000..63e7e61
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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.
+
+android_test {
+ name: "FrameworksResourceLoaderTestsOverlay",
+ sdk_version: "current",
+
+ aaptflags: ["--no-resource-removal"],
+}
diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
new file mode 100644
index 0000000..942f7da
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test.overlay"
+ >
+
+ <application android:hasCode="false" />
+
+ <overlay android:targetPackage="android.content.res.loader.test" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
new file mode 100644
index 0000000..348bb35
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="loader_path_change_test">Overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
new file mode 100644
index 0000000..efd71ee
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
new file mode 100644
index 0000000..d1211c5
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#B2D2F2"
+ />
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
new file mode 100644
index 0000000..d59059b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/layout/layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/res/values/strings.xml b/core/tests/ResourceLoaderTests/res/values/strings.xml
new file mode 100644
index 0000000..28b8f73
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="loader_path_change_test">Not overlaid</string>
+ <string name="split_overlaid">Not overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
new file mode 100644
index 0000000..5dd8a96
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
new file mode 100644
index 0000000..5a92ae9
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<!-- Mocks the framework package name so that AAPT2 assigns the correct package -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
new file mode 100755
index 0000000..885f681
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# Copyright (C) 2019 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.
+
+aapt2=$1
+soong_zip=$2
+genDir=$3
+FRAMEWORK_RES_APK=$4
+inDir=$5
+
+# (String name, boolean retainFiles = false, String... files)
+function compileAndLink {
+ moduleName=$1
+ mkdir "$genDir"/out/"$moduleName"
+
+ args=""
+ for arg in "${@:4}"; do
+ if [[ $arg == res* ]]; then
+ args="$args $inDir/$arg"
+ else
+ args="$args $arg"
+ fi
+ done
+
+ $aapt2 compile -o "$genDir"/out/"$moduleName" $args
+
+ $aapt2 link \
+ -I "$FRAMEWORK_RES_APK" \
+ --manifest "$inDir"/"$3" \
+ -o "$genDir"/out/"$moduleName"/apk.apk \
+ "$genDir"/out/"$moduleName"/*.flat \
+ --no-compress
+
+ unzip -qq "$genDir"/out/"$moduleName"/apk.apk -d "$genDir"/out/"$moduleName"/unzip
+
+ if [[ "$2" == "APK_WITHOUT_FILE" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+ zip -q -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+ cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+ elif [[ "$2" == "APK" || "$2" == "BOTH" ]]; then
+ cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+ fi
+
+ if [[ "$2" == "ARSC" || "$2" == "BOTH" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+ zip -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+ cp "$genDir"/out/"$moduleName"/unzip/resources.arsc "$genDir"/output/raw/"$moduleName"Arsc.arsc
+ fi
+}
+
+rm -r "$genDir"/out
+rm -r "$genDir"/output
+rm -r "$genDir"/temp
+
+mkdir "$genDir"/out
+mkdir -p "$genDir"/output/raw
+mkdir -p "$genDir"/temp/res/drawable-nodpi
+mkdir -p "$genDir"/temp/res/layout
+
+compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml
+compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml
+
+compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml
+compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml
+
+compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+
+compileAndLink layoutWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+compileAndLink layoutWithFile APK AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+
+cp -f "$inDir"/res/layout/layout_one.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutOne/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutOne.xml
+
+cp -f "$inDir"/res/layout/layout_two.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml
+
+drawableNoDpi="/res/drawable-nodpi"
+inDirDrawableNoDpi="$inDir$drawableNoDpi"
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableOne.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableOne/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableOne.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableTwo.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapBlue.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png
+
+$soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
new file mode 100644
index 0000000..f3e53d7
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
new file mode 100644
index 0000000..5231d17
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
new file mode 100644
index 0000000..671d6d0
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
new file mode 100644
index 0000000..f1a93d2
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#A3C3E3"
+ />
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
new file mode 100644
index 0000000..7c455a5
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#3A3C3E"
+ />
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
new file mode 100644
index 0000000..d59059b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
new file mode 100644
index 0000000..ede3838
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
new file mode 100644
index 0000000..d8bff90
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
new file mode 100644
index 0000000..a552431
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="layout" name="activity_list_item" id="0x01090000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
new file mode 100644
index 0000000..69ecf23
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="dimen" name="app_icon_size" id="0x01050000" />
+ <dimen name="app_icon_size">564716dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
new file mode 100644
index 0000000..4d55def
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="dimen" name="app_icon_size" id="0x01050000" />
+ <dimen name="app_icon_size">565717dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
new file mode 100644
index 0000000..b5b4dfd
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="drawable" name="ic_delete" id="0x0108001d" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
new file mode 100644
index 0000000..4962a07
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="layout" name="layout" id="0x7f020000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
new file mode 100644
index 0000000..38b152b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="drawable" name="non_asset_bitmap" id="0x7f010000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
new file mode 100644
index 0000000..bdd6f58
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="drawable" name="non_asset_drawable" id="0x7f010001" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
new file mode 100644
index 0000000..4fc5272
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="string" name="cancel" id="0x01040000" />
+ <string name="cancel">SomeRidiculouslyUnlikelyStringOne</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
new file mode 100644
index 0000000..3604d7b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <public type="string" name="cancel" id="0x01040000" />
+ <string name="cancel">SomeRidiculouslyUnlikelyStringTwo</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/splits/Android.bp b/core/tests/ResourceLoaderTests/splits/Android.bp
new file mode 100644
index 0000000..4582808
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+ name: "FrameworksResourceLoaderTestsSplitTwo"
+}
diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
new file mode 100644
index 0000000..aad8c27
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ split="split_two"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
new file mode 100644
index 0000000..a367063
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="string" name="split_overlaid" id="0x7f040001" />
+ <string name="split_overlaid">Split TWO Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
new file mode 100644
index 0000000..b1bdc96
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import java.io.File
+
+class DirectoryResourceLoaderTest : ResourceLoaderTestBase() {
+
+ @get:Rule
+ val testName = TestName()
+
+ private lateinit var testDir: File
+ private lateinit var loader: ResourceLoader
+
+ @Before
+ fun setUpTestDir() {
+ testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+ loader = DirectoryResourceLoader(testDir)
+ }
+
+ @After
+ fun deleteTestFiles() {
+ testDir.deleteRecursively()
+ }
+
+ @Test
+ fun loadDrawableXml() {
+ "nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml"
+ val provider = openArsc("nonAssetDrawableOne")
+
+ fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable)
+ .color
+
+ assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2"))
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo(Color.parseColor("#A3C3E3"))
+ }
+
+ @Test
+ fun loadDrawableBitmap() {
+ "nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png"
+ val provider = openArsc("nonAssetBitmapGreen")
+
+ fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+ .bitmap.getColor(0, 0).toArgb()
+
+ assertThat(getValue()).isEqualTo(Color.RED)
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo(Color.GREEN)
+ }
+
+ @Test
+ fun loadXml() {
+ "layoutOne" writeTo "res/layout/layout.xml"
+ val provider = openArsc("layoutOne")
+
+ fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name
+
+ assertThat(getValue()).isEqualTo("FrameLayout")
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo("RelativeLayout")
+ }
+
+ private infix fun String.writeTo(path: String) {
+ val testFile = testDir.resolve(path)
+ testFile.parentFile!!.mkdirs()
+ resources.openRawResource(rawFile(this))
+ .copyTo(testFile.outputStream())
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
new file mode 100644
index 0000000..a6a8378
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.AssetManager
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.nio.file.Paths
+
+@RunWith(Parameterized::class)
+class ResourceLoaderAssetTest : ResourceLoaderTestBase() {
+
+ companion object {
+ private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt"
+ private const val TEST_TEXT = "some text"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun parameters(): Array<Array<out Any?>> {
+ val fromInputStream: ResourceLoader.(String) -> Any? = {
+ loadAsset(eq(it), anyInt())
+ }
+
+ val fromFileDescriptor: ResourceLoader.(String) -> Any? = {
+ loadAssetFd(eq(it))
+ }
+
+ val openAsset: AssetManager.() -> String? = {
+ open(BASE_TEST_PATH).reader().readText()
+ }
+
+ val openNonAsset: AssetManager.() -> String? = {
+ openNonAssetFd(BASE_TEST_PATH).readText()
+ }
+
+ return arrayOf(
+ arrayOf("assets", fromInputStream, openAsset),
+ arrayOf("", fromFileDescriptor, openNonAsset)
+ )
+ }
+ }
+
+ @get:Rule
+ val testName = TestName()
+
+ @JvmField
+ @field:Parameterized.Parameter(0)
+ var prefix: String? = null
+
+ @field:Parameterized.Parameter(1)
+ lateinit var loadAssetFunction: ResourceLoader.(String) -> Any?
+
+ @field:Parameterized.Parameter(2)
+ lateinit var openAssetFunction: AssetManager.() -> String?
+
+ private val testPath: String
+ get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString()
+
+ private fun ResourceLoader.loadAsset() = loadAssetFunction(testPath)
+
+ private fun AssetManager.openAsset() = openAssetFunction()
+
+ private lateinit var testDir: File
+
+ @Before
+ fun setUpTestDir() {
+ testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+ testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT)
+ }
+
+ @Test
+ fun multipleLoadersSearchesBackwards() {
+ // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
+ val loader = DirectoryResourceLoader(testDir)
+ val loaderWrapper = mock(ResourceLoader::class.java).apply {
+ doAnswer { loader.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
+ .`when`(this).loadAsset(anyString(), anyInt())
+ doAnswer { loader.loadAssetFd(it.arguments[0] as String) }
+ .`when`(this).loadAssetFd(anyString())
+ }
+
+ val one = loaderWrapper to ResourcesProvider.empty()
+ val two = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ inOrder(two.first, one.first).apply {
+ verify(two.first).loadAsset()
+ verify(one.first).loadAsset()
+ }
+ }
+
+ @Test(expected = FileNotFoundException::class)
+ fun failToFindThrowsFileNotFound() {
+ val one = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+ val two = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ @Test
+ fun throwingIOExceptionIsSkipped() {
+ val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+ val two = mockLoader {
+ doAnswer { throw IOException() }.`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun throwingNonIOExceptionCausesFailure() {
+ val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+ val two = mockLoader {
+ doAnswer { throw IllegalStateException() }.`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ private fun assertOpenedAsset() {
+ assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT)
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
new file mode 100644
index 0000000..e01e254
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.app.Activity
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Color
+import android.os.Bundle
+import android.os.ParcelFileDescriptor
+import android.widget.FrameLayout
+import androidx.test.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
+import androidx.test.runner.lifecycle.Stage
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.Arrays
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+import java.util.concurrent.TimeUnit
+
+// @Ignore("UiAutomation is crashing with not connected, not sure why")
+@RunWith(Parameterized::class)
+class ResourceLoaderChangesTest : ResourceLoaderTestBase() {
+
+ companion object {
+ private const val TIMEOUT = 30L
+ private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @get:Rule
+ val activityRule: ActivityTestRule<TestActivity> =
+ ActivityTestRule<TestActivity>(TestActivity::class.java, false, true)
+
+ // Redirect to the Activity's resources
+ override val resources: Resources
+ get() = activityRule.getActivity().resources
+
+ private val activity: TestActivity
+ get() = activityRule.getActivity()
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ @Before
+ @After
+ fun disableOverlay() {
+// enableOverlay(OVERLAY_PACKAGE, false)
+ }
+
+ @Test
+ fun activityRecreate() = verifySameBeforeAndAfter {
+ val oldActivity = activity
+ var newActivity: Activity? = null
+ instrumentation.runOnMainSync { oldActivity.recreate() }
+ instrumentation.waitForIdleSync()
+ instrumentation.runOnMainSync {
+ newActivity = ActivityLifecycleMonitorRegistry.getInstance()
+ .getActivitiesInStage(Stage.RESUMED)
+ .single()
+ }
+
+ assertThat(newActivity).isNotNull()
+ assertThat(newActivity).isNotSameAs(oldActivity)
+
+ // Return the new resources to assert on
+ return@verifySameBeforeAndAfter newActivity!!.resources
+ }
+
+ @Test
+ fun activityHandledOrientationChange() = verifySameBeforeAndAfter {
+ val latch = CountDownLatch(1)
+ val oldConfig = Configuration().apply { setTo(resources.configuration) }
+ var changedConfig: Configuration? = null
+
+ activity.callback = object : TestActivity.Callback {
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ changedConfig = newConfig
+ latch.countDown()
+ }
+ }
+
+ val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels }
+ val newRotation = if (isPortrait) {
+ UiAutomation.ROTATION_FREEZE_90
+ } else {
+ UiAutomation.ROTATION_FREEZE_0
+ }
+
+ instrumentation.uiAutomation.setRotation(newRotation)
+
+ assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+ assertThat(changedConfig).isNotEqualTo(oldConfig)
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ @Test
+ fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter {
+ assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid")
+
+ enableOverlay(OVERLAY_PACKAGE, true)
+
+ assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid")
+
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ @Test
+ fun enableOverlayChildContextUnaffected() {
+ val childContext = activity.createConfigurationContext(Configuration())
+ val childResources = childContext.resources
+ val originalValue = childResources.getString(android.R.string.cancel)
+ assertThat(childResources.getString(R.string.loader_path_change_test))
+ .isEqualTo("Not overlaid")
+
+ verifySameBeforeAndAfter {
+ enableOverlay(OVERLAY_PACKAGE, true)
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ // Loader not applied, but overlay change propagated
+ assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue)
+ assertThat(childResources.getString(R.string.loader_path_change_test))
+ .isEqualTo("Overlaid")
+ }
+
+ // All these tests assert for the exact same loaders/values, so extract that logic out
+ private fun verifySameBeforeAndAfter(block: () -> Resources) {
+ // TODO(chiuwinson): atest doesn't work with @Ignore, UiAutomation not connected error
+ Assume.assumeFalse(true)
+
+ val originalValue = resources.getString(android.R.string.cancel)
+
+ val loader = "stringOne".openLoader()
+ addLoader(loader)
+
+ val oldLoaders = resources.loaders
+ val oldValue = resources.getString(android.R.string.cancel)
+
+ assertThat(oldValue).isNotEqualTo(originalValue)
+
+ val newResources = block()
+
+ val newLoaders = newResources.loaders
+ val newValue = newResources.getString(android.R.string.cancel)
+
+ assertThat(newValue).isEqualTo(oldValue)
+ assertThat(newLoaders).isEqualTo(oldLoaders)
+ }
+
+ // Copied from overlaytests LocalOverlayManager
+ private fun enableOverlay(packageName: String, enable: Boolean) {
+ val executor = Executor { Thread(it).start() }
+ val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName
+ if (executeShellCommand("cmd overlay list").contains(pattern)) {
+ // nothing to do, overlay already in the requested state
+ return
+ }
+
+ val oldApkPaths = resources.assets.apkPaths
+ val task = FutureTask {
+ while (true) {
+ if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) {
+ return@FutureTask true
+ }
+ Thread.sleep(10)
+ }
+
+ @Suppress("UNREACHABLE_CODE")
+ return@FutureTask false
+ }
+
+ val command = if (enable) "enable" else "disable"
+ executeShellCommand("cmd overlay $command $packageName")
+ executor.execute(task)
+ assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+ }
+
+ private fun executeShellCommand(command: String): String {
+ val uiAutomation = instrumentation.uiAutomation
+ val pfd = uiAutomation.executeShellCommand(command)
+ return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() }
+ }
+}
+
+class TestActivity : Activity() {
+
+ var callback: Callback? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(FrameLayout(this).apply {
+ setBackgroundColor(Color.BLUE)
+ })
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ callback?.onConfigurationChanged(newConfig)
+ }
+
+ interface Callback {
+ fun onConfigurationChanged(newConfig: Configuration)
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
new file mode 100644
index 0000000..09fd27e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderDrawableTest : ResourceLoaderTestBase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @Test
+ fun matchingConfig() {
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+
+ addLoader(loader)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+
+ loader.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotEqualTo(original)
+ assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+ assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+ }
+
+ @Test
+ fun worseConfig() {
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ addLoader(loader)
+
+ updateConfiguration { densityDpi = 480 /* xhdpi */ }
+
+ getDrawable(android.R.drawable.ic_delete)
+
+ verify(loader.first, never()).loadDrawable(any(), anyInt(), anyInt(), any())
+ }
+
+ @Test
+ fun multipleLoaders() {
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loaderOne = "drawableMdpiWithoutFile".openLoader()
+ val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+ `when`(loaderTwo.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotEqualTo(original)
+ assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+ assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+ }
+
+ @Test(expected = Resources.NotFoundException::class)
+ fun multipleLoadersNoReturnWithoutFile() {
+ val loaderOne = "drawableMdpiWithoutFile".openLoader()
+ val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ try {
+ getDrawable(android.R.drawable.ic_delete)
+ } finally {
+ // We expect the call to fail because at least the loader won't resolve the overridden
+ // drawable, but we should still verify that both loaders were called before allowing
+ // the exception to propagate.
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+ }
+ }
+
+ @Test
+ fun multipleLoadersReturnWithFile() {
+ // Can't return a file if an ARSC
+ assumeThat(dataType, not(DataType.ARSC))
+
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loaderOne = "drawableMdpiWithFile".openLoader()
+ val loaderTwo = "drawableMdpiWithFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotNull()
+ assertThat(drawable).isInstanceOf(original.javaClass)
+ }
+
+ @Test
+ fun unhandledResourceIgnoresLoaders() {
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+ addLoader(loader)
+
+ getDrawable(android.R.drawable.ic_menu_add)
+
+ loader.verifyLoadDrawableNotCalled()
+
+ getDrawable(android.R.drawable.ic_delete)
+
+ loader.verifyLoadDrawableCalled()
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableCalled() {
+ verify(first).loadDrawable(
+ argThat {
+ it.density == 160 &&
+ it.resourceId == android.R.drawable.ic_delete &&
+ it.string == "res/drawable-mdpi-v4/ic_delete.png"
+ },
+ eq(android.R.drawable.ic_delete),
+ eq(0),
+ any()
+ )
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableNotCalled() {
+ verify(first, never()).loadDrawable(
+ any(),
+ anyInt(),
+ anyInt(),
+ any()
+ )
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
new file mode 100644
index 0000000..1ec2094
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderLayoutTest : ResourceLoaderTestBase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @Test
+ fun singleLoader() {
+ val original = getLayout(android.R.layout.activity_list_item)
+ val mockXml = mock(XmlResourceParser::class.java)
+ val loader = "layoutWithoutFile".openLoader()
+ `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+
+ addLoader(loader)
+
+ val layout = getLayout(android.R.layout.activity_list_item)
+ loader.verifyLoadLayoutCalled()
+
+ assertThat(layout).isNotEqualTo(original)
+ assertThat(layout).isSameAs(mockXml)
+ }
+
+ @Test
+ fun multipleLoaders() {
+ val original = getLayout(android.R.layout.activity_list_item)
+ val loaderOne = "layoutWithoutFile".openLoader()
+ val loaderTwo = "layoutWithoutFile".openLoader()
+
+ val mockXml = mock(XmlResourceParser::class.java)
+ `when`(loaderTwo.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+
+ addLoader(loaderOne, loaderTwo)
+
+ val layout = getLayout(android.R.layout.activity_list_item)
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+
+ assertThat(layout).isNotEqualTo(original)
+ assertThat(layout).isSameAs(mockXml)
+ }
+
+ @Test(expected = Resources.NotFoundException::class)
+ fun multipleLoadersNoReturnWithoutFile() {
+ val loaderOne = "layoutWithoutFile".openLoader()
+ val loaderTwo = "layoutWithoutFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ try {
+ getLayout(android.R.layout.activity_list_item)
+ } finally {
+ // We expect the call to fail because at least one loader must resolve the overridden
+ // layout, but we should still verify that both loaders were called before allowing
+ // the exception to propagate.
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+ }
+ }
+
+ @Test
+ fun multipleLoadersReturnWithFile() {
+ // Can't return a file if an ARSC
+ assumeThat(dataType, not(DataType.ARSC))
+
+ val loaderOne = "layoutWithFile".openLoader()
+ val loaderTwo = "layoutWithFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ val xml = getLayout(android.R.layout.activity_list_item)
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+
+ assertThat(xml).isNotNull()
+ }
+
+ @Test
+ fun unhandledResourceIgnoresLoaders() {
+ val loader = "layoutWithoutFile".openLoader()
+ val mockXml = mock(XmlResourceParser::class.java)
+ `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+ addLoader(loader)
+
+ getLayout(android.R.layout.preference_category)
+
+ verify(loader.first, never())
+ .loadXmlResourceParser(anyString(), anyInt())
+
+ getLayout(android.R.layout.activity_list_item)
+
+ loader.verifyLoadLayoutCalled()
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutCalled() {
+ verify(first).loadXmlResourceParser(
+ "res/layout/activity_list_item.xml",
+ android.R.layout.activity_list_item
+ )
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutNotCalled() {
+ verify(first, never()).loadXmlResourceParser(
+ anyString(),
+ anyInt()
+ )
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
new file mode 100644
index 0000000..5af453d
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetManager
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Before
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.io.Closeable
+
+abstract class ResourceLoaderTestBase {
+
+ open lateinit var dataType: DataType
+
+ protected lateinit var context: Context
+ protected open val resources: Resources
+ get() = context.resources
+ protected open val assets: AssetManager
+ get() = resources.assets
+
+ // Track opened streams and ResourcesProviders to close them after testing
+ private val openedObjects = mutableListOf<Closeable>()
+
+ @Before
+ fun setUpBase() {
+ context = InstrumentationRegistry.getTargetContext()
+ }
+
+ @After
+ fun removeAllLoaders() {
+ resources.setLoaders(null)
+ openedObjects.forEach {
+ try {
+ it.close()
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ protected fun getString(@StringRes stringRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getString(stringRes) }
+
+ protected fun getDrawable(@DrawableRes drawableRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getDrawable(drawableRes) }
+
+ protected fun getLayout(@LayoutRes layoutRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getLayout(layoutRes) }
+
+ protected fun getDimensionPixelSize(@DimenRes dimenRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getDimensionPixelSize(dimenRes) }
+
+ private fun <T> logResolution(debugLog: Boolean = false, block: Resources.() -> T): T {
+ if (debugLog) {
+ resources.assets.setResourceResolutionLoggingEnabled(true)
+ }
+
+ var thrown = false
+
+ try {
+ return resources.block()
+ } catch (t: Throwable) {
+ // No good way to log to test output other than throwing an exception
+ if (debugLog) {
+ thrown = true
+ throw IllegalStateException(resources.assets.lastResourceResolution, t)
+ } else {
+ throw t
+ }
+ } finally {
+ if (!thrown && debugLog) {
+ throw IllegalStateException(resources.assets.lastResourceResolution)
+ }
+ }
+ }
+
+ protected fun updateConfiguration(block: Configuration.() -> Unit) {
+ val configuration = Configuration().apply {
+ setTo(resources.configuration)
+ block()
+ }
+
+ resources.updateConfiguration(configuration, resources.displayMetrics)
+ }
+
+ protected fun String.openLoader(
+ dataType: DataType = this@ResourceLoaderTestBase.dataType
+ ): Pair<ResourceLoader, ResourcesProvider> = when (dataType) {
+ DataType.APK -> {
+ mock(ResourceLoader::class.java) to context.copiedRawFile("${this}Apk").use {
+ ResourcesProvider.loadFromApk(it)
+ }.also { openedObjects += it }
+ }
+ DataType.ARSC -> {
+ mock(ResourceLoader::class.java) to openArsc(this)
+ }
+ DataType.SPLIT -> {
+ mock(ResourceLoader::class.java) to ResourcesProvider.loadFromSplit(context, this)
+ }
+ DataType.ASSET -> mockLoader {
+ doAnswer { byteInputStream() }.`when`(it)
+ .loadAsset(eq("assets/Asset.txt"), anyInt())
+ }
+ DataType.ASSET_FD -> mockLoader {
+ doAnswer {
+ val file = context.filesDir.resolve("Asset.txt")
+ file.writeText(this)
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+ }.`when`(it).loadAssetFd("assets/Asset.txt")
+ }
+ DataType.NON_ASSET -> mockLoader {
+ doAnswer {
+ val file = context.filesDir.resolve("NonAsset.txt")
+ file.writeText(this)
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+ }.`when`(it).loadAssetFd("NonAsset.txt")
+ }
+ DataType.NON_ASSET_DRAWABLE -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it).loadDrawable(argThat { value ->
+ value.type == TypedValue.TYPE_STRING &&
+ value.resourceId == 0x7f010001 &&
+ value.string == "res/drawable-nodpi-v4/non_asset_drawable.xml"
+ }, eq(0x7f010001), anyInt(), ArgumentMatchers.any())
+
+ doAnswer { context.copiedRawFile(this) }.`when`(it)
+ .loadAssetFd("res/drawable-nodpi-v4/non_asset_drawable.xml")
+ }
+ DataType.NON_ASSET_BITMAP -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it).loadDrawable(argThat { value ->
+ value.type == TypedValue.TYPE_STRING &&
+ value.resourceId == 0x7f010000 &&
+ value.string == "res/drawable-nodpi-v4/non_asset_bitmap.png"
+ }, eq(0x7f010000), anyInt(), ArgumentMatchers.any())
+
+ doAnswer { resources.openRawResourceFd(rawFile(this)).createInputStream() }
+ .`when`(it)
+ .loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt())
+ }
+ DataType.NON_ASSET_LAYOUT -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it)
+ .loadXmlResourceParser("res/layout/layout.xml", 0x7f020000)
+
+ doAnswer { context.copiedRawFile(this) }.`when`(it)
+ .loadAssetFd("res/layout/layout.xml")
+ }
+ }
+
+ protected fun mockLoader(
+ provider: ResourcesProvider = ResourcesProvider.empty(),
+ block: (ResourceLoader) -> Unit = {}
+ ): Pair<ResourceLoader, ResourcesProvider> {
+ return mock(ResourceLoader::class.java, Utils.ANSWER_THROWS)
+ .apply(block) to provider
+ }
+
+ protected fun openArsc(rawName: String): ResourcesProvider {
+ return context.copiedRawFile("${rawName}Arsc")
+ .use { ResourcesProvider.loadFromArsc(it) }
+ .also { openedObjects += it }
+ }
+
+ // This specifically uses addLoader so both behaviors are tested
+ protected fun addLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ pairs.forEach { resources.addLoader(it.first, it.second) }
+ }
+
+ protected fun setLoaders(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ resources.setLoaders(pairs.map { android.util.Pair(it.first, it.second) })
+ }
+
+ protected fun addLoader(pair: Pair<out ResourceLoader, ResourcesProvider>, index: Int) {
+ resources.addLoader(pair.first, pair.second, index)
+ }
+
+ protected fun removeLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ pairs.forEach { resources.removeLoader(it.first) }
+ }
+
+ protected fun getLoaders(): MutableList<Pair<ResourceLoader, ResourcesProvider>> {
+ // Cast instead of toMutableList to maintain the same object
+ return resources.getLoaders() as MutableList<Pair<ResourceLoader, ResourcesProvider>>
+ }
+
+ enum class DataType {
+ APK,
+ ARSC,
+ SPLIT,
+ ASSET,
+ ASSET_FD,
+ NON_ASSET,
+ NON_ASSET_DRAWABLE,
+ NON_ASSET_BITMAP,
+ NON_ASSET_LAYOUT,
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
new file mode 100644
index 0000000..017552a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
+ * the behavior being verified isn't specific to any resource type. As long as it can pass an
+ * equals check.
+ *
+ * Currently tests strings and dimens since String and any Number seemed most relevant to verify.
+ */
+@RunWith(Parameterized::class)
+class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
+
+ companion object {
+ @Parameterized.Parameters(name = "{1} {0}")
+ @JvmStatic
+ fun parameters(): Array<Any> {
+ val parameters = mutableListOf<Parameter>()
+
+ // R.string
+ parameters += Parameter(
+ { getString(android.R.string.cancel) },
+ "stringOne", { "SomeRidiculouslyUnlikelyStringOne" },
+ "stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" },
+ listOf(DataType.APK, DataType.ARSC)
+ )
+
+ // R.dimen
+ parameters += Parameter(
+ { resources.getDimensionPixelSize(android.R.dimen.app_icon_size) },
+ "dimenOne", { 564716.dpToPx(resources) },
+ "dimenTwo", { 565717.dpToPx(resources) },
+ listOf(DataType.APK, DataType.ARSC)
+ )
+
+ // File in the assets directory
+ parameters += Parameter(
+ { assets.open("Asset.txt").reader().readText() },
+ "assetOne", { "assetOne" },
+ "assetTwo", { "assetTwo" },
+ listOf(DataType.ASSET)
+ )
+
+ // From assets directory returning file descriptor
+ parameters += Parameter(
+ { assets.openFd("Asset.txt").readText() },
+ "assetOne", { "assetOne" },
+ "assetTwo", { "assetTwo" },
+ listOf(DataType.ASSET_FD)
+ )
+
+ // From root directory returning file descriptor
+ parameters += Parameter(
+ { assets.openNonAssetFd("NonAsset.txt").readText() },
+ "NonAssetOne", { "NonAssetOne" },
+ "NonAssetTwo", { "NonAssetTwo" },
+ listOf(DataType.NON_ASSET)
+ )
+
+ // Asset as compiled XML drawable
+ parameters += Parameter(
+ { (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color },
+ "nonAssetDrawableOne", { Color.parseColor("#A3C3E3") },
+ "nonAssetDrawableTwo", { Color.parseColor("#3A3C3E") },
+ listOf(DataType.NON_ASSET_DRAWABLE)
+ )
+
+ // Asset as compiled bitmap drawable
+ parameters += Parameter(
+ {
+ (getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+ .bitmap.getColor(0, 0).toArgb()
+ },
+ "nonAssetBitmapGreen", { Color.GREEN },
+ "nonAssetBitmapBlue", { Color.BLUE },
+ listOf(DataType.NON_ASSET_BITMAP)
+ )
+
+ // Asset as compiled XML layout
+ parameters += Parameter(
+ { getLayout(R.layout.layout).advanceToRoot().name },
+ "layoutOne", { "RelativeLayout" },
+ "layoutTwo", { "LinearLayout" },
+ listOf(DataType.NON_ASSET_LAYOUT)
+ )
+
+ // Isolated resource split
+ parameters += Parameter(
+ { getString(R.string.split_overlaid) },
+ "split_one", { "Split ONE Overlaid" },
+ "split_two", { "Split TWO Overlaid" },
+ listOf(DataType.SPLIT)
+ )
+
+ return parameters.flatMap { parameter ->
+ parameter.dataTypes.map { dataType ->
+ arrayOf(dataType, parameter)
+ }
+ }.toTypedArray()
+ }
+ }
+
+ @Suppress("LateinitVarOverridesLateinitVar")
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @field:Parameterized.Parameter(1)
+ lateinit var parameter: Parameter
+
+ private val valueOne by lazy { parameter.valueOne(this) }
+ private val valueTwo by lazy { parameter.valueTwo(this) }
+
+ private fun openOne() = parameter.loaderOne.openLoader()
+ private fun openTwo() = parameter.loaderTwo.openLoader()
+
+ // Class method for syntax highlighting purposes
+ private fun getValue() = parameter.getValue(this)
+
+ @Test
+ fun verifyValueUniqueness() {
+ // Ensure the parameters are valid in case of coding errors
+ assertNotEquals(valueOne, getValue())
+ assertNotEquals(valueTwo, getValue())
+ assertNotEquals(valueOne, valueTwo)
+ }
+
+ @Test
+ fun addMultipleLoaders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun setMultipleLoaders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ setLoaders(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ setLoaders()
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun getLoadersContainsAll() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertThat(getLoaders()).containsAllOf(testOne, testTwo)
+ }
+
+ @Test
+ fun getLoadersDoesNotLeakMutability() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ val loaders = getLoaders()
+ loaders += testTwo
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun alreadyAddedThrows() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+ addLoader(testTwo)
+ addLoader(testOne)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun alreadyAddedAndSetThrows() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+ addLoader(testTwo)
+ setLoaders(testTwo)
+ }
+
+ @Test
+ fun repeatedRemoveSucceeds() {
+ val originalValue = getValue()
+ val testOne = openOne()
+
+ addLoader(testOne)
+
+ assertNotEquals(originalValue, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun addToFront() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 0)
+
+ assertEquals(valueOne, getValue())
+
+ // Remove top loader, so previously added to front should now resolve
+ removeLoader(testOne)
+ assertEquals(valueTwo, getValue())
+ }
+
+ @Test
+ fun addToEnd() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 1)
+
+ assertEquals(valueTwo, getValue())
+ }
+
+ @Test(expected = IndexOutOfBoundsException::class)
+ fun addPastEnd() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 2)
+ }
+
+ @Test(expected = IndexOutOfBoundsException::class)
+ fun addBeforeFront() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, -1)
+ }
+
+ @Test
+ fun reorder() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(valueTwo, getValue())
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ data class Parameter(
+ val getValue: ResourceLoaderValuesTest.() -> Any,
+ val loaderOne: String,
+ val valueOne: ResourceLoaderValuesTest.() -> Any,
+ val loaderTwo: String,
+ val valueTwo: ResourceLoaderValuesTest.() -> Any,
+ val dataTypes: List<DataType>
+ ) {
+ override fun toString(): String {
+ val prefix = loaderOne.commonPrefixWith(loaderTwo)
+ return "$prefix${loaderOne.removePrefix(prefix)}|${loaderTwo.removePrefix(prefix)}"
+ }
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
new file mode 100644
index 0000000..df2d09a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetFileDescriptor
+import android.content.res.Resources
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import org.mockito.Answers
+import org.mockito.stubbing.Answer
+import org.xmlpull.v1.XmlPullParser
+import java.io.File
+
+// Enforce use of [android.util.Pair] instead of Kotlin's so it matches the ResourceLoader APIs
+typealias Pair<F, S> = android.util.Pair<F, S>
+infix fun <A, B> A.to(that: B): Pair<A, B> = Pair.create(this, that)!!
+
+object Utils {
+ val ANSWER_THROWS = Answer<Any> {
+ when (val name = it.method.name) {
+ "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+ else -> throw UnsupportedOperationException("$name with " +
+ "${it.arguments?.joinToString()} should not be called")
+ }
+ }
+}
+
+fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ this.toFloat(),
+ resources.displayMetrics
+).toInt()
+
+fun AssetFileDescriptor.readText() = createInputStream().reader().readText()
+
+fun rawFile(fileName: String) = R.raw::class.java.getDeclaredField(fileName).getInt(null)
+
+fun XmlPullParser.advanceToRoot() = apply {
+ while (next() != XmlPullParser.START_TAG) {
+ // Empty
+ }
+}
+
+fun Context.copiedRawFile(fileName: String): ParcelFileDescriptor {
+ return resources.openRawResourceFd(rawFile(fileName)).use { asset ->
+ // AssetManager doesn't expose a direct file descriptor to the asset, so copy it to
+ // an individual file so one can be created manually.
+ val copiedFile = File(filesDir, fileName)
+ asset.createInputStream().use { input ->
+ copiedFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE)
+ }
+}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 4ae9494..fb0dd46 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -20,52 +20,44 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
-import static android.app.admin.PasswordMetrics.getActualRequiredQuality;
-import static android.app.admin.PasswordMetrics.getMinimumMetrics;
-import static android.app.admin.PasswordMetrics.getTargetQualityMetrics;
import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
+import static android.app.admin.PasswordMetrics.validatePasswordMetrics;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.PasswordValidationError;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
/** Unit tests for {@link PasswordMetrics}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class PasswordMetricsTest {
-
- @Test
- public void testIsDefault() {
- final PasswordMetrics metrics = new PasswordMetrics();
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, metrics.quality);
- assertEquals(0, metrics.length);
- assertEquals(0, metrics.letters);
- assertEquals(0, metrics.upperCase);
- assertEquals(0, metrics.lowerCase);
- assertEquals(0, metrics.numeric);
- assertEquals(0, metrics.symbols);
- assertEquals(0, metrics.nonLetter);
- }
-
@Test
public void testParceling() {
- final int quality = 0;
+ final int credType = CREDENTIAL_TYPE_PASSWORD;
final int length = 1;
final int letters = 2;
final int upperCase = 3;
@@ -73,20 +65,21 @@
final int numeric = 5;
final int symbols = 6;
final int nonLetter = 7;
+ final int nonNumeric = 8;
+ final int seqLength = 9;
final Parcel parcel = Parcel.obtain();
- final PasswordMetrics metrics;
+ PasswordMetrics metrics = new PasswordMetrics(credType, length, letters, upperCase,
+ lowerCase, numeric, symbols, nonLetter, nonNumeric, seqLength);
try {
- new PasswordMetrics(
- quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter)
- .writeToParcel(parcel, 0);
+ metrics.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
metrics = PasswordMetrics.CREATOR.createFromParcel(parcel);
} finally {
parcel.recycle();
}
- assertEquals(quality, metrics.quality);
+ assertEquals(credType, metrics.credType);
assertEquals(length, metrics.length);
assertEquals(letters, metrics.letters);
assertEquals(upperCase, metrics.upperCase);
@@ -94,7 +87,8 @@
assertEquals(numeric, metrics.numeric);
assertEquals(symbols, metrics.symbols);
assertEquals(nonLetter, metrics.nonLetter);
-
+ assertEquals(nonNumeric, metrics.nonNumeric);
+ assertEquals(seqLength, metrics.seqLength);
}
@Test
@@ -111,23 +105,6 @@
}
@Test
- public void testComputeForPassword_quality() {
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
- PasswordMetrics.computeForPassword("a1".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
- PasswordMetrics.computeForPassword("a".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
- PasswordMetrics.computeForPassword("*~&%$".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
- PasswordMetrics.computeForPassword("1".getBytes()).quality);
- // contains a long sequence so isn't complex
- assertEquals(PASSWORD_QUALITY_NUMERIC,
- PasswordMetrics.computeForPassword("1234".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- PasswordMetrics.computeForPassword("".getBytes()).quality);
- }
-
- @Test
public void testMaxLengthSequence() {
assertEquals(4, PasswordMetrics.maxLengthSequence("1234".getBytes()));
assertEquals(5, PasswordMetrics.maxLengthSequence("13579".getBytes()));
@@ -142,69 +119,15 @@
}
@Test
- public void testEquals() {
- PasswordMetrics metrics0 = new PasswordMetrics();
- PasswordMetrics metrics1 = new PasswordMetrics();
- assertNotEquals(metrics0, null);
- assertNotEquals(metrics0, new Object());
- assertEquals(metrics0, metrics0);
- assertEquals(metrics0, metrics1);
-
- assertEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4));
-
- assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
-
- assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4));
-
- metrics0 = PasswordMetrics.computeForPassword("1234abcd,./".getBytes());
- metrics1 = PasswordMetrics.computeForPassword("1234abcd,./".getBytes());
- assertEquals(metrics0, metrics1);
- metrics1.letters++;
- assertNotEquals(metrics0, metrics1);
- metrics1.letters--;
- metrics1.upperCase++;
- assertNotEquals(metrics0, metrics1);
- metrics1.upperCase--;
- metrics1.lowerCase++;
- assertNotEquals(metrics0, metrics1);
- metrics1.lowerCase--;
- metrics1.numeric++;
- assertNotEquals(metrics0, metrics1);
- metrics1.numeric--;
- metrics1.symbols++;
- assertNotEquals(metrics0, metrics1);
- metrics1.symbols--;
- metrics1.nonLetter++;
- assertNotEquals(metrics0, metrics1);
- metrics1.nonLetter--;
- assertEquals(metrics0, metrics1);
-
-
- }
-
- @Test
- public void testConstructQuality() {
- PasswordMetrics expected = new PasswordMetrics();
- expected.quality = PASSWORD_QUALITY_COMPLEX;
-
- PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX);
-
- assertEquals(expected, actual);
- }
-
- @Test
public void testDetermineComplexity_none() {
assertEquals(PASSWORD_COMPLEXITY_NONE,
- PasswordMetrics.computeForPassword("".getBytes()).determineComplexity());
+ new PasswordMetrics(CREDENTIAL_TYPE_NONE).determineComplexity());
}
@Test
public void testDetermineComplexity_lowSomething() {
assertEquals(PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity());
+ new PasswordMetrics(CREDENTIAL_TYPE_PATTERN).determineComplexity());
}
@Test
@@ -324,122 +247,84 @@
}
@Test
- public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() {
- PasswordMetrics metrics =
- getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC);
-
- assertTrue(metrics.isDefault());
+ public void testMerge_single() {
+ PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Collections.singletonList(metrics)).credType);
}
@Test
- public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() {
- PasswordMetrics metrics =
- getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
+ public void testMerge_credentialTypes() {
+ PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ assertEquals(CREDENTIAL_TYPE_PATTERN,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, pattern}))
+ .credType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, password}))
+ .credType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{password, pattern}))
+ .credType);
}
@Test
- public void testGetTargetQualityMetrics_highComplexityNumericComplex() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ public void testValidatePasswordMetrics_credentialTypes() {
+ PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 8, metrics.length);
+ // To pass minimal length check.
+ password.length = 4;
+
+ // No errors expected, credential is of stronger or equal type.
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, none));
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, pattern));
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, password));
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, pattern));
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, password));
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, password));
+
+ // Now actual credential type is weaker than required:
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, none),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, none),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, pattern),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
}
- @Test
- public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC);
+ /**
+ * @param expected sequense of validation error codes followed by requirement values, must have
+ * even number of elements. Empty means no errors.
+ */
+ private void assertValidationErrors(
+ List<PasswordValidationError> actualErrors, int... expected) {
+ assertEquals("Test programming error: content shoud have even number of elements",
+ 0, expected.length % 2);
+ assertEquals("wrong number of validation errors", expected.length / 2, actualErrors.size());
+ HashMap<Integer, Integer> errorMap = new HashMap<>();
+ for (PasswordValidationError error : actualErrors) {
+ errorMap.put(error.errorCode, error.requirement);
+ }
- assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
- }
-
- @Test
- public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
- }
-
- @Test
- public void testGetActualRequiredQuality_nonComplex() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_NUMERIC_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNone() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNumeric() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ true,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresLetters() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ true);
-
- assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ true,
- /* requiresLettersOrSymbols= */ true);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual);
- }
-
- @Test
- public void testGetMinimumMetrics_userInputStricter() {
- PasswordMetrics metrics = getMinimumMetrics(
- PASSWORD_COMPLEXITY_HIGH,
- PASSWORD_QUALITY_ALPHANUMERIC,
- PASSWORD_QUALITY_NUMERIC,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
- assertEquals(/* expected= */ 6, metrics.length);
- }
-
- @Test
- public void testGetMinimumMetrics_actualRequiredQualityStricter() {
- PasswordMetrics metrics = getMinimumMetrics(
- PASSWORD_COMPLEXITY_HIGH,
- PASSWORD_QUALITY_UNSPECIFIED,
- PASSWORD_QUALITY_NUMERIC,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 8, metrics.length);
+ for (int i = 0; i < expected.length / 2; i++) {
+ final int expectedError = expected[i * 2];
+ final int expectedRequirement = expected[i * 2 + 1];
+ assertTrue("error expected but not reported: " + expectedError,
+ errorMap.containsKey(expectedError));
+ assertEquals("unexpected requirement for error: " + expectedError,
+ Integer.valueOf(expectedRequirement), errorMap.get(expectedError));
+ }
}
}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
new file mode 100644
index 0000000..e951054
--- /dev/null
+++ b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PasswordPolicyTest {
+
+ public static final int TEST_VALUE = 10;
+
+ @Test
+ public void testGetMinMetrics_unspecified() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_UNSPECIFIED);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_NONE, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_something() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_SOMETHING);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PATTERN, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_biometricWeak() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_BIOMETRIC_WEAK);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PATTERN, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_numeric() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric); // numeric can doesn't really require digits.
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_numericDefaultLength() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
+ policy.length = 0; // reset to default
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(0, minMetrics.length);
+ }
+
+ @Test
+ public void testGetMinMetrics_numericComplex() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(PasswordMetrics.MAX_ALLOWED_SEQUENCE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_alphabetic() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_ALPHABETIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(1, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_alphanumeric() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_ALPHANUMERIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(1, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(1, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_complex() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_COMPLEX);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(TEST_VALUE, minMetrics.letters);
+ assertEquals(TEST_VALUE, minMetrics.lowerCase);
+ assertEquals(TEST_VALUE, minMetrics.upperCase);
+ assertEquals(TEST_VALUE, minMetrics.symbols);
+ assertEquals(TEST_VALUE, minMetrics.numeric);
+ assertEquals(TEST_VALUE, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_complexDefault() {
+ PasswordPolicy policy = new PasswordPolicy();
+ policy.quality = PASSWORD_QUALITY_COMPLEX;
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(1, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(1, minMetrics.symbols);
+ assertEquals(1, minMetrics.numeric);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ private PasswordPolicy testPolicy(int quality) {
+ PasswordPolicy result = new PasswordPolicy();
+ result.quality = quality;
+ result.length = TEST_VALUE;
+ result.letters = TEST_VALUE;
+ result.lowerCase = TEST_VALUE;
+ result.upperCase = TEST_VALUE;
+ result.numeric = TEST_VALUE;
+ result.symbols = TEST_VALUE;
+ result.nonLetter = TEST_VALUE;
+ return result;
+ }
+}
diff --git a/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/util/TimestampedValueTest.java
index 6e3ab79..6fc2400 100644
--- a/core/tests/coretests/src/android/util/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/util/TimestampedValueTest.java
@@ -55,12 +55,12 @@
TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, stringValue);
+ parcel.writeParcelable(stringValue, 0);
parcel.setDataPosition(0);
TimestampedValue<String> stringValueCopy =
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+ parcel.readParcelable(null /* classLoader */);
assertEquals(stringValue, stringValueCopy);
} finally {
parcel.recycle();
@@ -72,12 +72,12 @@
TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, stringValue);
+ parcel.writeParcelable(stringValue, 0);
parcel.setDataPosition(0);
- TimestampedValue<Object> stringValueCopy =
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, Object.class);
+ TimestampedValue<String> stringValueCopy =
+ parcel.readParcelable(null /* classLoader */);
assertEquals(stringValue, stringValueCopy);
} finally {
parcel.recycle();
@@ -85,15 +85,15 @@
}
@Test
- public void testParceling_valueClassIncompatible() {
- TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
+ public void testParceling_valueClassNotParcelable() {
+ // This class is not one supported by Parcel.writeValue().
+ class NotParcelable {}
+
+ TimestampedValue<NotParcelable> notParcelableValue =
+ new TimestampedValue<>(1000, new NotParcelable());
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, stringValue);
-
- parcel.setDataPosition(0);
-
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, Double.class);
+ parcel.writeParcelable(notParcelableValue, 0);
fail();
} catch (RuntimeException expected) {
} finally {
@@ -106,12 +106,11 @@
TimestampedValue<String> nullValue = new TimestampedValue<>(1000, null);
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, nullValue);
+ parcel.writeParcelable(nullValue, 0);
parcel.setDataPosition(0);
- TimestampedValue<Object> nullValueCopy =
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+ TimestampedValue<String> nullValueCopy = parcel.readParcelable(null /* classLoader */);
assertEquals(nullValue, nullValueCopy);
} finally {
parcel.recycle();
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index abee1da2..7b405434 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -345,9 +345,9 @@
accessibilityShortcutController.performAccessibilityShortcut();
accessibilityShortcutController.performAccessibilityShortcut();
verify(mToast).show();
- assertEquals(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
mLayoutParams.privateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
+ & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
new file mode 100644
index 0000000..5eec91c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+
+import android.test.AndroidTestCase;
+
+import java.util.Arrays;
+
+
+public class LockscreenCredentialTest extends AndroidTestCase {
+
+ public void testEmptyCredential() {
+ LockscreenCredential empty = LockscreenCredential.createNone();
+
+ assertTrue(empty.isNone());
+ assertEquals(0, empty.size());
+ assertNotNull(empty.getCredential());
+
+ assertFalse(empty.isPassword());
+ assertFalse(empty.isPattern());
+ }
+
+ public void testPasswordCredential() {
+ LockscreenCredential password = LockscreenCredential.createPassword("password");
+
+ assertTrue(password.isPassword());
+ assertEquals(8, password.size());
+ assertTrue(Arrays.equals("password".getBytes(), password.getCredential()));
+
+ assertFalse(password.isNone());
+ assertFalse(password.isPattern());
+ }
+
+ public void testPatternCredential() {
+ LockscreenCredential pattern = LockscreenCredential.createPattern(Arrays.asList(
+ LockPatternView.Cell.of(0, 0),
+ LockPatternView.Cell.of(0, 1),
+ LockPatternView.Cell.of(0, 2),
+ LockPatternView.Cell.of(1, 2),
+ LockPatternView.Cell.of(2, 2)
+ ));
+
+ assertTrue(pattern.isPattern());
+ assertEquals(5, pattern.size());
+ assertTrue(Arrays.equals("12369".getBytes(), pattern.getCredential()));
+
+ assertFalse(pattern.isNone());
+ assertFalse(pattern.isPassword());
+ }
+
+ public void testPasswordOrNoneCredential() {
+ assertEquals(LockscreenCredential.createNone(),
+ LockscreenCredential.createPasswordOrNone(null));
+ assertEquals(LockscreenCredential.createNone(),
+ LockscreenCredential.createPasswordOrNone(""));
+ assertEquals(LockscreenCredential.createPassword("abcd"),
+ LockscreenCredential.createPasswordOrNone("abcd"));
+ }
+
+ public void testSanitize() {
+ LockscreenCredential password = LockscreenCredential.createPassword("password");
+ password.zeroize();
+
+ try {
+ password.isNone();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+
+ try {
+ password.isPattern();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ try {
+ password.isPassword();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ try {
+ password.size();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ try {
+ password.getCredential();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ }
+
+ public void testEquals() {
+ assertEquals(LockscreenCredential.createNone(), LockscreenCredential.createNone());
+ assertEquals(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createPassword("1234"));
+ assertEquals(LockscreenCredential.createPin("4321"),
+ LockscreenCredential.createPin("4321"));
+ assertEquals(createPattern("1234"), createPattern("1234"));
+
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createNone());
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createPassword("4321"));
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ createPattern("1234"));
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createPin("1234"));
+
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ LockscreenCredential.createNone());
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ LockscreenCredential.createPin("2222"));
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ createPattern("1111"));
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ LockscreenCredential.createPassword("1111"));
+
+ assertNotSame(createPattern("5678"),
+ LockscreenCredential.createNone());
+ assertNotSame(createPattern("5678"),
+ createPattern("1234"));
+ assertNotSame(createPattern("5678"),
+ LockscreenCredential.createPassword("5678"));
+ assertNotSame(createPattern("5678"),
+ LockscreenCredential.createPin("5678"));
+ }
+
+ public void testDuplicate() {
+ LockscreenCredential credential;
+
+ credential = LockscreenCredential.createNone();
+ assertEquals(credential, credential.duplicate());
+ credential = LockscreenCredential.createPassword("abcd");
+ assertEquals(credential, credential.duplicate());
+ credential = LockscreenCredential.createPin("1234");
+ assertEquals(credential, credential.duplicate());
+ credential = createPattern("5678");
+ assertEquals(credential, credential.duplicate());
+ }
+
+ private LockscreenCredential createPattern(String patternString) {
+ return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ patternString.getBytes()));
+ }
+}
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index d36a826..61281ee 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -16,17 +16,30 @@
-->
<permissions>
<privapp-permissions package="com.google.android.car.kitchensink">
+ <permission name="android.permission.ACCESS_NETWORK_STATE"/>
+ <permission name="android.permission.ACCESS_WIFI_STATE"/>
+ <permission name="android.permission.ACTIVITY_EMBEDDING"/>
+ <permission name="android.permission.INJECT_EVENTS"/>
+ <!-- use for CarServiceUnitTest and CarServiceTest -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <!-- use for CarServiceUnitTest -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.LOCATION_HARDWARE"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <!-- use for CarServiceTest -->
+ <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.PROVIDE_TRUST_AGENT"/>
+ <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.REBOOT"/>
+ <!-- use for CarServiceTest -->
+ <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
index 987c3b4..7296cfd 100644
--- a/data/etc/framework-sysconfig.xml
+++ b/data/etc/framework-sysconfig.xml
@@ -29,6 +29,8 @@
'service' attribute here is a flattened ComponentName string. -->
<backup-transport-whitelisted-service
service="com.android.localtransport/.LocalTransportService" />
+ <backup-transport-whitelisted-service
+ service="com.android.encryptedlocaltransport/.EncryptedLocalTransportService" />
<!-- Whitelist Shell to use the bugreport API -->
<bugreport-whitelisted package="com.android.shell" />
diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
index f1ba3f6..07a5617 100644
--- a/data/etc/hiddenapi-package-whitelist.xml
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -56,7 +56,7 @@
<!-- TODO (b/141954427): Remove networkstack -->
<hidden-api-whitelisted-app package="com.android.networkstack" />
<!-- TODO (b/141954427): Remove wifistack -->
- <hidden-api-whitelisted-app package="com.android.server.wifistack" />
+ <hidden-api-whitelisted-app package="com.android.wifi" />
<hidden-api-whitelisted-app package="com.android.smspush" />
<hidden-api-whitelisted-app package="com.android.spare_parts" />
<hidden-api-whitelisted-app package="com.android.statementservice" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 51136b9..4b4e416 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -121,6 +121,7 @@
<permission name="android.permission.APPROVE_INCIDENT_REPORTS"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+ <permission name="android.permission.PACKAGE_USAGE_STATS" />
</privapp-permissions>
<privapp-permissions package="com.android.phone">
@@ -354,11 +355,12 @@
<permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
</privapp-permissions>
- <privapp-permissions package="com.android.server.wifistack">
+ <privapp-permissions package="com.android.wifi">
<permission name="android.permission.CHANGE_CONFIGURATION"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index 454dceb..4226e08 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -83,8 +83,8 @@
################################
# Copies the font configuration file into system/etc for the product as fonts.xml.
-# In the case where $(ADDITIONAL_FONTS_FILE) is defined, the content of $(ADDITIONAL_FONTS_FILE)
-# is added to the $(AOSP_FONTS_FILE).
+# Additional fonts should be installed to /product/fonts/ alongside a corresponding
+# fonts_customiztion.xml in /product/etc/
include $(CLEAR_VARS)
LOCAL_MODULE := fonts.xml
diff --git a/graphics/java/android/graphics/pdf/TEST_MAPPING b/graphics/java/android/graphics/pdf/TEST_MAPPING
new file mode 100644
index 0000000..d763598
--- /dev/null
+++ b/graphics/java/android/graphics/pdf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPdfTestCases"
+ }
+ ]
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index bf23634..254456c 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -350,7 +350,7 @@
* access manually.
*/
public static final String KEY_ALIAS_SELECTION_DENIED =
- "alias-selection-denied-ef829e15-210a-409d-96c9-ee684fc41972";
+ "android:alias-selection-denied";
/**
* Returns an {@code Intent} that can be used for credential
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index eeaefc5..a34a6c0 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -166,7 +166,10 @@
static_libs: common_test_libs + ["liblog", "libz"],
},
},
- data: ["tests/data/**/*.apk"],
+ data: [
+ "tests/data/**/*.apk",
+ "tests/data/**/*.arsc",
+ ],
test_suites: ["device-tests"],
}
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index cf2ef30..b309621 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -42,12 +42,16 @@
ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle,
const std::string& path,
- time_t last_mod_time)
- : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time) {
+ time_t last_mod_time,
+ bool for_loader)
+ : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time),
+ for_loader(for_loader) {
}
-std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
- return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
+std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system,
+ bool for_loader) {
+ return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/,
+ for_loader);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
@@ -76,9 +80,21 @@
std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
const std::string& friendly_name,
- bool system, bool force_shared_lib) {
+ bool system, bool force_shared_lib,
+ bool for_loader) {
return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
- system, force_shared_lib);
+ system, force_shared_lib, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path,
+ bool for_loader) {
+ return LoadArscImpl({} /*fd*/, path, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(unique_fd fd,
+ const std::string& friendly_name,
+ bool for_loader) {
+ return LoadArscImpl(std::move(fd), friendly_name, for_loader);
}
std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
@@ -104,7 +120,8 @@
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
- std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
+ std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library,
+ bool for_loader) {
::ZipArchiveHandle unmanaged_handle;
int32_t result;
if (fd >= 0) {
@@ -123,7 +140,8 @@
time_t last_mod_time = getFileModDate(path.c_str());
// Wrap the handle in a unique_ptr so it gets automatically closed.
- std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time));
+ std::unique_ptr<ApkAssets>
+ loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, for_loader));
// Find the resource table.
::ZipEntry entry;
@@ -152,7 +170,7 @@
reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
loaded_apk->resources_asset_->getLength());
loaded_apk->loaded_arsc_ =
- LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
+ LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library, for_loader);
if (loaded_apk->loaded_arsc_ == nullptr) {
LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
@@ -162,8 +180,53 @@
return std::move(loaded_apk);
}
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd,
+ const std::string& path,
+ bool for_loader) {
+ std::unique_ptr<Asset> resources_asset;
+
+ if (fd >= 0) {
+ resources_asset = std::unique_ptr<Asset>(Asset::createFromFd(fd.release(), nullptr,
+ Asset::AccessMode::ACCESS_BUFFER));
+ } else {
+ resources_asset = CreateAssetFromFile(path);
+ }
+
+ if (resources_asset == nullptr) {
+ LOG(ERROR) << "Failed to open ARSC '" << path;
+ return {};
+ }
+
+ time_t last_mod_time = getFileModDate(path.c_str());
+
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, path, last_mod_time, for_loader));
+ loaded_apk->resources_asset_ = std::move(resources_asset);
+
+ const StringPiece data(
+ reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
+ loaded_apk->resources_asset_->getLength());
+ loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, nullptr, false, false, for_loader);
+ if (loaded_apk->loaded_arsc_ == nullptr) {
+ LOG(ERROR) << "Failed to load '" << kResourcesArsc << path;
+ return {};
+ }
+
+ // Need to force a move for mingw32.
+ return std::move(loaded_apk);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(bool for_loader) {
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "", -1, for_loader));
+ loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
+ // Need to force a move for mingw32.
+ return std::move(loaded_apk);
+}
+
std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
- CHECK(zip_handle_ != nullptr);
+ // If this is a resource loader from an .arsc, there will be no zip handle
+ if (zip_handle_ == nullptr) {
+ return {};
+ }
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
@@ -205,7 +268,10 @@
bool ApkAssets::ForEachFile(const std::string& root_path,
const std::function<void(const StringPiece&, FileType)>& f) const {
- CHECK(zip_handle_ != nullptr);
+ // If this is a resource loader from an .arsc, there will be no zip handle
+ if (zip_handle_ == nullptr) {
+ return false;
+ }
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
@@ -252,6 +318,11 @@
}
bool ApkAssets::IsUpToDate() const {
+ // Loaders are invalidated by the app, not the system, so assume up to date
+ if (for_loader) {
+ return true;
+ }
+
return last_mod_time_ == getFileModDate(path_.c_str());
}
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 92125c9..c132f34 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -133,14 +133,24 @@
*/
/*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
{
+ return createFromFd(open(fileName, O_RDONLY | O_BINARY), fileName, mode);
+}
+
+/*
+ * Create a new Asset from a file on disk. There is a fair chance that
+ * the file doesn't actually exist.
+ *
+ * We can use "mode" to decide how we want to go about it.
+ */
+/*static*/ Asset* Asset::createFromFd(const int fd, const char* fileName, AccessMode mode)
+{
+ if (fd < 0) {
+ return NULL;
+ }
+
_FileAsset* pAsset;
status_t result;
off64_t length;
- int fd;
-
- fd = open(fileName, O_RDONLY | O_BINARY);
- if (fd < 0)
- return NULL;
/*
* Under Linux, the lseek fails if we actually opened a directory. To
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index eec49df..e914f37 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -493,8 +493,12 @@
type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
- // If the package is an overlay, then even configurations that are the same MUST be chosen.
+
+ // If the package is an overlay or custom loader,
+ // then even configurations that are the same MUST be chosen.
const bool package_is_overlay = loaded_package->IsOverlay();
+ const bool package_is_loader = loaded_package->IsCustomLoader();
+ const bool should_overlay = package_is_overlay || package_is_loader;
if (use_fast_path) {
const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
@@ -508,10 +512,28 @@
if (best_config == nullptr) {
resolution_type = Resolution::Step::Type::INITIAL;
} else if (this_config.isBetterThan(*best_config, desired_config)) {
- resolution_type = Resolution::Step::Type::BETTER_MATCH;
- } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
- resolution_type = Resolution::Step::Type::OVERLAID;
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH;
+ }
+ } else if (should_overlay && this_config.compare(*best_config) == 0) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+ } else if (package_is_overlay) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ }
} else {
+ if (resource_resolution_logging_enabled_) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::SKIPPED_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::SKIPPED;
+ }
+ resolution_steps.push_back(Resolution::Step{resolution_type,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
+ }
continue;
}
@@ -520,6 +542,16 @@
const ResTable_type* type = filtered_group.types[i];
const uint32_t offset = LoadedPackage::GetEntryOffset(type, local_entry_idx);
if (offset == ResTable_type::NO_ENTRY) {
+ if (resource_resolution_logging_enabled_) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::NO_ENTRY_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::NO_ENTRY;
+ }
+ resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
+ }
continue;
}
@@ -554,9 +586,17 @@
if (best_config == nullptr) {
resolution_type = Resolution::Step::Type::INITIAL;
} else if (this_config.isBetterThan(*best_config, desired_config)) {
- resolution_type = Resolution::Step::Type::BETTER_MATCH;
- } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
- resolution_type = Resolution::Step::Type::OVERLAID;
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH;
+ }
+ } else if (should_overlay && this_config.compare(*best_config) == 0) {
+ if (package_is_overlay) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ } else if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+ }
} else {
continue;
}
@@ -678,9 +718,27 @@
case Resolution::Step::Type::BETTER_MATCH:
prefix = "Found better";
break;
+ case Resolution::Step::Type::BETTER_MATCH_LOADER:
+ prefix = "Found better in loader";
+ break;
case Resolution::Step::Type::OVERLAID:
prefix = "Overlaid";
break;
+ case Resolution::Step::Type::OVERLAID_LOADER:
+ prefix = "Overlaid by loader";
+ break;
+ case Resolution::Step::Type::SKIPPED:
+ prefix = "Skipped";
+ break;
+ case Resolution::Step::Type::SKIPPED_LOADER:
+ prefix = "Skipped loader";
+ break;
+ case Resolution::Step::Type::NO_ENTRY:
+ prefix = "No entry";
+ break;
+ case Resolution::Step::Type::NO_ENTRY_LOADER:
+ prefix = "No entry for loader";
+ break;
}
if (!prefix.empty()) {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 72873ab..882dc0d 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -401,7 +401,9 @@
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap,
- bool system, bool load_as_shared_library) {
+ bool system,
+ bool load_as_shared_library,
+ bool for_loader) {
ATRACE_NAME("LoadedPackage::Load");
std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
@@ -430,6 +432,10 @@
loaded_package->overlay_ = true;
}
+ if (for_loader) {
+ loaded_package->custom_loader_ = true;
+ }
+
if (header->header.headerSize >= sizeof(ResTable_package)) {
uint32_t type_id_offset = dtohl(header->typeIdOffset);
if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
@@ -696,7 +702,7 @@
}
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
- bool load_as_shared_library) {
+ bool load_as_shared_library, bool for_loader) {
const ResTable_header* header = chunk.header<ResTable_header>();
if (header == nullptr) {
LOG(ERROR) << "RES_TABLE_TYPE too small.";
@@ -735,7 +741,11 @@
packages_seen++;
std::unique_ptr<const LoadedPackage> loaded_package =
- LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
+ LoadedPackage::Load(child_chunk,
+ loaded_idmap,
+ system_,
+ load_as_shared_library,
+ for_loader);
if (!loaded_package) {
return false;
}
@@ -758,9 +768,11 @@
}
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
- const LoadedIdmap* loaded_idmap, bool system,
- bool load_as_shared_library) {
- ATRACE_NAME("LoadedArsc::LoadTable");
+ const LoadedIdmap* loaded_idmap,
+ bool system,
+ bool load_as_shared_library,
+ bool for_loader) {
+ ATRACE_NAME("LoadedArsc::Load");
// Not using make_unique because the constructor is private.
std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
@@ -771,7 +783,10 @@
const Chunk chunk = iter.Next();
switch (chunk.type()) {
case RES_TABLE_TYPE:
- if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
+ if (!loaded_arsc->LoadTable(chunk,
+ loaded_idmap,
+ load_as_shared_library,
+ for_loader)) {
return {};
}
break;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 49fc82b..625b6820 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -40,7 +40,8 @@
// Creates an ApkAssets.
// If `system` is true, the package is marked as a system package, and allows some functions to
// filter out this package when computing what configurations/resources are available.
- static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false);
+ static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false,
+ bool for_loader = false);
// Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
// If `system` is true, the package is marked as a system package, and allows some functions to
@@ -63,7 +64,21 @@
// If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd,
const std::string& friendly_name, bool system,
- bool force_shared_lib);
+ bool force_shared_lib,
+ bool for_loader = false);
+
+ // Creates an empty wrapper ApkAssets from the given path which points to an .arsc.
+ static std::unique_ptr<const ApkAssets> LoadArsc(const std::string& path,
+ bool for_loader = false);
+
+ // Creates an empty wrapper ApkAssets from the given file descriptor which points to an .arsc,
+ // Takes ownership of the file descriptor.
+ static std::unique_ptr<const ApkAssets> LoadArsc(base::unique_fd fd,
+ const std::string& friendly_name,
+ bool resource_loader = false);
+
+ // Creates a totally empty ApkAssets with no resources table and no file entries.
+ static std::unique_ptr<const ApkAssets> LoadEmpty(bool resource_loader = false);
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
@@ -86,24 +101,33 @@
bool IsUpToDate() const;
+ // Creates an Asset from any file on the file system.
+ static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_idmap,
- bool system, bool load_as_shared_library);
+ bool system, bool load_as_shared_library,
+ bool resource_loader = false);
- // Creates an Asset from any file on the file system.
- static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+ static std::unique_ptr<const ApkAssets> LoadArscImpl(base::unique_fd fd,
+ const std::string& path,
+ bool resource_loader = false);
- ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time);
+ ApkAssets(ZipArchiveHandle unmanaged_handle,
+ const std::string& path,
+ time_t last_mod_time,
+ bool for_loader = false);
- using ZipArchivePtr = std::unique_ptr<ZipArchive, void(*)(ZipArchiveHandle)>;
+ using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
ZipArchivePtr zip_handle_;
const std::string path_;
time_t last_mod_time_;
+ bool for_loader;
std::unique_ptr<Asset> resources_asset_;
std::unique_ptr<Asset> idmap_asset_;
std::unique_ptr<const LoadedArsc> loaded_arsc_;
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 9d12a35..053dbb7 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -121,6 +121,11 @@
*/
const char* getAssetSource(void) const { return mAssetSource.string(); }
+ /*
+ * Create the asset from a file descriptor.
+ */
+ static Asset* createFromFd(const int fd, const char* fileName, AccessMode mode);
+
protected:
/*
* Adds this Asset to the global Asset list for debugging and
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index de46081..c7348b1 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -382,7 +382,13 @@
enum class Type {
INITIAL,
BETTER_MATCH,
- OVERLAID
+ BETTER_MATCH_LOADER,
+ OVERLAID,
+ OVERLAID_LOADER,
+ SKIPPED,
+ SKIPPED_LOADER,
+ NO_ENTRY,
+ NO_ENTRY_LOADER,
};
// Marks what kind of override this step was.
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 950f541..1a56876 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -137,7 +137,8 @@
static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap, bool system,
- bool load_as_shared_library);
+ bool load_as_shared_library,
+ bool load_as_custom_loader);
~LoadedPackage();
@@ -187,6 +188,11 @@
return overlay_;
}
+ // Returns true if this package is a custom loader and should behave like an overlay
+ inline bool IsCustomLoader() const {
+ return custom_loader_;
+ }
+
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
// compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
@@ -260,6 +266,7 @@
bool dynamic_ = false;
bool system_ = false;
bool overlay_ = false;
+ bool custom_loader_ = false;
bool defines_overlayable_ = false;
ByteBucketArray<TypeSpecPtr> type_specs_;
@@ -282,7 +289,8 @@
static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data,
const LoadedIdmap* loaded_idmap = nullptr,
bool system = false,
- bool load_as_shared_library = false);
+ bool load_as_shared_library = false,
+ bool for_loader = false);
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<const LoadedArsc> CreateEmpty();
@@ -311,7 +319,19 @@
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
LoadedArsc() = default;
- bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
+ bool LoadTable(
+ const Chunk& chunk,
+ const LoadedIdmap* loaded_idmap,
+ bool load_as_shared_library,
+ bool for_loader
+ );
+
+ static std::unique_ptr<const LoadedArsc> LoadData(std::unique_ptr<LoadedArsc>& loaded_arsc,
+ const char* data,
+ size_t length,
+ const LoadedIdmap* loaded_idmap = nullptr,
+ bool load_as_shared_library = false,
+ bool for_loader = false);
ResStringPool global_string_pool_;
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d58e8d2..fd57a92 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -25,6 +25,7 @@
#include "data/overlayable/R.h"
#include "data/sparse/R.h"
#include "data/styles/R.h"
+#include "data/system/R.h"
namespace app = com::android::app;
namespace basic = com::android::basic;
@@ -387,6 +388,39 @@
ASSERT_EQ(map.at("OverlayableResources2"), "overlay://com.android.overlayable");
}
+TEST(LoadedArscTest, LoadCustomLoader) {
+ std::string contents;
+
+ std::unique_ptr<Asset>
+ asset = ApkAssets::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+
+ MockLoadedIdmap loaded_idmap;
+ const StringPiece data(
+ reinterpret_cast<const char*>(asset->getBuffer(true /*wordAligned*/)),
+ asset->getLength());
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc =
+ LoadedArsc::Load(data, nullptr, false, false, true);
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(android::R::string::cancel));
+ ASSERT_THAT(package, NotNull());
+ EXPECT_THAT(package->GetPackageName(), StrEq("android"));
+ EXPECT_THAT(package->GetPackageId(), Eq(0x01));
+
+ const uint8_t type_index = get_type_id(android::R::string::cancel) - 1;
+ const uint16_t entry_index = get_entry_id(android::R::string::cancel);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+
+ const ResTable_type* type = type_spec->types[0];
+ ASSERT_THAT(type, NotNull());
+ ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
+}
+
// structs with size fields (like Res_value, ResTable_entry) should be
// backwards and forwards compatible (aka checking the size field against
// sizeof(Res_value) might not be backwards compatible.
diff --git a/libs/androidfw/tests/data/loader/resources.arsc b/libs/androidfw/tests/data/loader/resources.arsc
new file mode 100644
index 0000000..2c881f2
--- /dev/null
+++ b/libs/androidfw/tests/data/loader/resources.arsc
Binary files differ
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
index becb388..3741074 100644
--- a/libs/androidfw/tests/data/system/R.h
+++ b/libs/androidfw/tests/data/system/R.h
@@ -40,6 +40,12 @@
number = 0x01030000, // sv
};
};
+
+ struct string {
+ enum : uint32_t {
+ cancel = 0x01040000,
+ };
+ };
};
} // namespace android
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ae90f11..61b72cf 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -26,8 +26,10 @@
// a problem
"-Wno-free-nonheap-object",
- // clang's warning is broken, see: https://llvm.org/bugs/show_bug.cgi?id=21629
- "-Wno-missing-braces",
+ // Clang is producing non-determistic binary when the new pass manager is
+ // enabled. Disable the new PM as a temporary workaround.
+ // b/142372146
+ "-fno-experimental-new-pass-manager",
],
include_dirs: [
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index 7d0b687..030a20f 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -27,7 +27,7 @@
// Smaller than INT_MIN/INT_MAX because we offset these values
// and thus don't want to be adding offsets to INT_MAX, that's bad
#define DIRTY_MIN (-0x7ffffff - 1)
-#define DIRTY_MAX (0x7ffffff)
+#define DIRTY_MAX (0x8000000)
namespace android {
namespace uirenderer {
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index c41023e..944ebf9 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -42,8 +42,8 @@
@Nullable private volatile T mListener;
private Registration(Executor executor, T listener) {
- Preconditions.checkArgument(listener != null);
- Preconditions.checkArgument(executor != null);
+ Preconditions.checkArgument(listener != null, "invalid null listener/callback");
+ Preconditions.checkArgument(executor != null, "invalid null executor");
mExecutor = executor;
mListener = listener;
}
@@ -83,16 +83,18 @@
return addInternal(listener, executor);
}
- protected final boolean addInternal(Object listener, Handler handler) throws RemoteException {
+ protected final boolean addInternal(@NonNull Object listener, @NonNull Handler handler)
+ throws RemoteException {
return addInternal(listener, new HandlerExecutor(handler));
}
- protected final boolean addInternal(Object listener, Executor executor) throws RemoteException {
+ protected final boolean addInternal(@NonNull Object listener, @NonNull Executor executor)
+ throws RemoteException {
+ Preconditions.checkArgument(listener != null, "invalid null listener/callback");
return addInternal(listener, new Registration<>(executor, convertKey(listener)));
}
private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
- Preconditions.checkNotNull(key);
Preconditions.checkNotNull(registration);
synchronized (mListeners) {
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index d06ba12..daa2e08 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -42,11 +42,11 @@
interface ILocationManager
{
void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
- in PendingIntent intent, String packageName);
+ in PendingIntent intent, String packageName, String listenerIdentifier);
void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName);
void requestGeofence(in LocationRequest request, in Geofence geofence,
- in PendingIntent intent, String packageName);
+ in PendingIntent intent, String packageName, String listenerIdentifier);
void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
Location getLastLocation(in LocationRequest request, String packageName);
@@ -64,22 +64,23 @@
boolean sendNiResponse(int notifId, int userResponse);
- boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener, in String packageName);
+ boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
+ String packageName, String listenerIdentifier);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
in String packageName);
long getGnssCapabilities(in String packageName);
void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
- boolean addGnssNavigationMessageListener(
- in IGnssNavigationMessageListener listener,
- in String packageName);
+ boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
+ String packageName, String listenerIdentifier);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
int getGnssYearOfHardware();
String getGnssHardwareModelName();
int getGnssBatchSize(String packageName);
- boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName);
+ boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName,
+ String listenerIdentifier);
void removeGnssBatchingCallback();
boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
void flushGnssBatch(String packageName);
@@ -92,6 +93,7 @@
String getBestProvider(in Criteria criteria, boolean enabledOnly);
ProviderProperties getProviderProperties(String provider);
boolean isProviderPackage(String packageName);
+ List<String> getProviderPackages(String provider);
void setExtraLocationControllerPackage(String packageName);
String getExtraLocationControllerPackage();
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9c36d76..27274d1 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -16,6 +16,7 @@
package android.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -79,6 +80,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
/**
@@ -1208,14 +1211,16 @@
}
/**
- * Attaches an extra {@link Location} to this Location.
+ * Attaches an extra {@link Location} to this Location. This is useful for location providers
+ * to set the {@link #EXTRA_NO_GPS_LOCATION} extra to provide coarse locations for clients.
*
* @param key the key associated with the Location extra
* @param value the Location to attach
* @hide
*/
- @UnsupportedAppUsage
- public void setExtraLocation(String key, Location value) {
+ @TestApi
+ @SystemApi
+ public void setExtraLocation(@Nullable String key, @Nullable Location value) {
if (mExtras == null) {
mExtras = new Bundle();
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 90e29df..0b3e1c3 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -51,6 +51,7 @@
import com.android.internal.location.ProviderProperties;
import com.android.internal.util.Preconditions;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
@@ -542,6 +543,23 @@
}
/**
+ * Create a string that allows an app to identify a listener
+ *
+ * @param listener The listener
+ *
+ * @return A identifying string
+ */
+ private static String getListenerIdentifier(@NonNull Object listener) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(listener.getClass().getName());
+ sb.append('@');
+ sb.append(Integer.toHexString(System.identityHashCode(listener)));
+
+ return sb.toString();
+ }
+
+ /**
* Register for a single location update using the named provider and
* a callback.
*
@@ -981,7 +999,7 @@
boolean registered = false;
try {
mService.requestLocationUpdates(locationRequest, transport, null,
- mContext.getPackageName());
+ mContext.getPackageName(), getListenerIdentifier(listener));
registered = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1026,7 +1044,7 @@
try {
mService.requestLocationUpdates(locationRequest, null, pendingIntent,
- mContext.getPackageName());
+ mContext.getPackageName(), getListenerIdentifier(pendingIntent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1235,6 +1253,24 @@
}
/**
+ * Returns a list of packages associated with the given provider,
+ * and an empty list if no package is associated with the provider.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ @Nullable
+ public List<String> getProviderPackages(@NonNull String provider) {
+ try {
+ return mService.getProviderPackages(provider);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return Collections.emptyList();
+ }
+ }
+
+ /**
* Sends additional commands to a location provider. Can be used to support provider specific
* extensions to the Location Manager API.
*
@@ -1471,7 +1507,8 @@
Geofence fence = Geofence.createCircle(latitude, longitude, radius);
LocationRequest request = new LocationRequest().setExpireIn(expiration);
try {
- mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+ mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
+ getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1548,7 +1585,8 @@
Preconditions.checkArgument(fence != null, "invalid null geofence");
try {
- mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+ mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
+ getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1762,6 +1800,7 @@
* @param handler a handler with a looper that the callback runs on
* @return true if the listener was successfully added
*
+ * @throws IllegalArgumentException if callback is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1781,10 +1820,12 @@
/**
* Registers a GNSS status callback.
*
- * @param callback GNSS status callback object to register
* @param executor the executor that the callback runs on
+ * @param callback GNSS status callback object to register
* @return true if the listener was successfully added
*
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if callback is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1853,6 +1894,8 @@
* @param listener a {@link OnNmeaMessageListener} object to register
* @param handler a handler with the looper that the listener runs on.
* @return true if the listener was successfully added
+ *
+ * @throws IllegalArgumentException if listener is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1874,6 +1917,9 @@
* @param listener a {@link OnNmeaMessageListener} object to register
* @param executor the {@link Executor} that the listener runs on.
* @return true if the listener was successfully added
+ *
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if listener is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1946,6 +1992,9 @@
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
* @param handler the handler that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@@ -1966,6 +2015,10 @@
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
* @param executor the executor that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@@ -1983,6 +2036,9 @@
*
* @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
* measurement corrections to be injected into the GNSS chipset.
+ *
+ * @throws IllegalArgumentException if measurementCorrections is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
* @hide
*/
@SystemApi
@@ -2057,6 +2113,9 @@
* @param callback a {@link GnssNavigationMessage.Callback} object to register.
* @param handler the handler that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssNavigationMessageCallback(
@@ -2078,6 +2137,10 @@
* @param callback a {@link GnssNavigationMessage.Callback} object to register.
* @param executor the looper that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssNavigationMessageCallback(
@@ -2538,7 +2601,7 @@
mListenerTransport = new GnssMeasurementsListener();
return mService.addGnssMeasurementsListener(mListenerTransport,
- mContext.getPackageName());
+ mContext.getPackageName(), "gnss measurement callback");
}
@Override
@@ -2574,7 +2637,7 @@
mListenerTransport = new GnssNavigationMessageListener();
return mService.addGnssNavigationMessageListener(mListenerTransport,
- mContext.getPackageName());
+ mContext.getPackageName(), "gnss navigation callback");
}
@Override
@@ -2609,7 +2672,8 @@
Preconditions.checkState(mListenerTransport == null);
mListenerTransport = new BatchedLocationCallback();
- return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName());
+ return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName(),
+ "batched location callback");
}
@Override
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 5341d07..33ddfa7 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2776,7 +2776,7 @@
updateImageSizeValues(in, IFD_TYPE_THUMBNAIL);
// Check if each image data is in valid position.
- validateImages(in);
+ validateImages();
if (mMimeType == IMAGE_TYPE_PEF) {
// PEF files contain a MakerNote data, which contains the data for ColorSpace tag.
@@ -3143,8 +3143,11 @@
// 2.1. Integers and byte order
in.setByteOrder(ByteOrder.BIG_ENDIAN);
+ int bytesRead = 0;
+
// Skip the signature bytes
in.seek(PNG_SIGNATURE.length);
+ bytesRead += PNG_SIGNATURE.length;
try {
while (true) {
@@ -3159,12 +3162,14 @@
// See PNG (Portable Network Graphics) Specification, Version 1.2,
// 3.2. Chunk layout
int length = in.readInt();
+ bytesRead += 4;
byte[] type = new byte[PNG_CHUNK_LENGTH_BYTE_LENGTH];
if (in.read(type) != type.length) {
throw new IOException("Encountered invalid length while parsing PNG chunk"
+ "type");
}
+ bytesRead += PNG_CHUNK_LENGTH_BYTE_LENGTH;
if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
// IEND marks the end of the image.
@@ -3177,12 +3182,17 @@
+ "type: " + byteArrayToHexString(type));
}
readExifSegment(data, IFD_TYPE_PRIMARY);
+
+ validateImages();
break;
} else {
// Skip to next chunk
in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH);
+ bytesRead += length + PNG_CHUNK_CRC_BYTE_LENGTH;
}
}
+ // Save offset values for handleThumbnailFromJfif() function
+ mExifOffset = bytesRead;
} catch (EOFException e) {
// Should not reach here. Will only reach here if the file is corrupted or
// does not follow the PNG specifications
@@ -3675,7 +3685,7 @@
int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder);
if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF
- || mMimeType == IMAGE_TYPE_RW2) {
+ || mMimeType == IMAGE_TYPE_RW2 || mMimeType == IMAGE_TYPE_PNG) {
thumbnailOffset += mExifOffset;
} else if (mMimeType == IMAGE_TYPE_ORF) {
// Update offset value since RAF files have IFD data preceding MakerNote data.
@@ -3819,12 +3829,13 @@
}
// Validate primary, preview, thumbnail image data by comparing image size
- private void validateImages(InputStream in) throws IOException {
+ private void validateImages() throws IOException {
// Swap images based on size (primary > preview > thumbnail)
swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_PREVIEW);
swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_THUMBNAIL);
swapBasedOnImageSize(IFD_TYPE_PREVIEW, IFD_TYPE_THUMBNAIL);
+ // TODO (b/142296453): Revise image width/height setting logic
// Check if image has PixelXDimension/PixelYDimension tags, which contain valid image
// sizes, excluding padding at the right end or bottom end of the image to make sure that
// the values are multiples of 64. See JEITA CP-3451C Table 5 and Section 4.8.1. B.
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index f132cef..66764c7 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -27,4 +27,6 @@
void selectRoute(String packageName, String id);
void unselectRoute(String packageName, String id);
void notifyControlRequestSent(String id, in Intent request);
+ void requestSetVolume(String id, int volume);
+ void requestUpdateVolume(String id, int delta);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 81213b9..7b7a34e 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -46,6 +46,8 @@
void registerClient2(IMediaRouter2Client client, String packageName);
void unregisterClient2(IMediaRouter2Client client);
void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request);
+ void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume);
+ void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
/**
* Changes the selected route of the client.
*
@@ -66,4 +68,9 @@
*/
void selectClientRoute2(IMediaRouter2Manager manager, String packageName,
in @nullable MediaRoute2Info route);
+
+ void requestSetVolume2Manager(IMediaRouter2Manager manager,
+ in MediaRoute2Info route, int volume);
+ void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+ in MediaRoute2Info route, int direction);
}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 26e7936..91d644b 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -3750,8 +3751,11 @@
public static final int DolbyVisionProfileDvheStn = 0x20;
public static final int DolbyVisionProfileDvheDth = 0x40;
public static final int DolbyVisionProfileDvheDtb = 0x80;
- public static final int DolbyVisionProfileDvheSt = 0x100;
- public static final int DolbyVisionProfileDvavSe = 0x200;
+ public static final int DolbyVisionProfileDvheSt = 0x100;
+ public static final int DolbyVisionProfileDvavSe = 0x200;
+ /** Dolby Vision AV1 profile */
+ @SuppressLint("AllUpper")
+ public static final int DolbyVisionProfileDvav110 = 0x400;
public static final int DolbyVisionLevelHd24 = 0x1;
public static final int DolbyVisionLevelHd30 = 0x2;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index f421029..7ed431d 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -1028,8 +1028,6 @@
* @see MediaFormat#COLOR_STANDARD_BT601_PAL
* @see MediaFormat#COLOR_STANDARD_BT601_NTSC
* @see MediaFormat#COLOR_STANDARD_BT2020
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_STANDARD = 35;
@@ -1040,8 +1038,6 @@
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
* @see MediaFormat#COLOR_TRANSFER_ST2084
* @see MediaFormat#COLOR_TRANSFER_HLG
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_TRANSFER = 36;
@@ -1050,8 +1046,6 @@
*
* @see MediaFormat#COLOR_RANGE_LIMITED
* @see MediaFormat#COLOR_RANGE_FULL
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_RANGE = 37;
// Add more here...
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index e8e0f82..58deff2 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -81,6 +81,20 @@
public abstract void onControlRequest(String routeId, Intent request);
/**
+ * Called when requestSetVolume is called on a route of the provider
+ * @param routeId the id of the route
+ * @param volume the target volume
+ */
+ public abstract void onSetVolume(String routeId, int volume);
+
+ /**
+ * Called when requestUpdateVolume is called on a route of the provider
+ * @param routeId id of the route
+ * @param delta the delta to add to the current volume
+ */
+ public abstract void onUpdateVolume(String routeId, int delta);
+
+ /**
* Updates provider info and publishes routes
*/
public final void setProviderInfo(MediaRoute2ProviderInfo info) {
@@ -130,5 +144,17 @@
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
MediaRoute2ProviderService.this, id, request));
}
+
+ @Override
+ public void requestSetVolume(String id, int volume) {
+ mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
+ MediaRoute2ProviderService.this, id, volume));
+ }
+
+ @Override
+ public void requestUpdateVolume(String id, int delta) {
+ mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
+ MediaRoute2ProviderService.this, id, delta));
+ }
}
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index ed35ef6..aca40d8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -265,6 +265,54 @@
}
}
+ /**
+ * Requests a volume change for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+ */
+ public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestSetVolume2(client, route, volume);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
+ /**
+ * Requests an incremental volume update for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param delta The delta to add to the current volume.
+ */
+ public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestUpdateVolume2(client, route, delta);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
@GuardedBy("mCallbackRecords")
private int findCallbackRecordIndexLocked(Callback callback) {
final int count = mCallbackRecords.size();
@@ -310,6 +358,7 @@
List<MediaRoute2Info> outRoutes) {
if (provider == null || !provider.isValid()) {
Log.w(TAG, "Ignoring invalid provider : " + provider);
+ return;
}
final Collection<MediaRoute2Info> routes = provider.getRoutes();
@@ -321,10 +370,21 @@
if (!route.supportsControlCategory(controlCategories)) {
continue;
}
+ MediaRoute2Info preRoute = findRouteById(route.getId());
+ if (!route.equals(preRoute)) {
+ notifyRouteChanged(route);
+ }
outRoutes.add(route);
}
}
+ MediaRoute2Info findRouteById(String id) {
+ for (MediaRoute2Info route : mRoutes) {
+ if (route.getId().equals(id)) return route;
+ }
+ return null;
+ }
+
void notifyRouteListChanged(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
@@ -332,10 +392,18 @@
}
}
+ void notifyRouteChanged(MediaRoute2Info route) {
+ for (CallbackRecord record: mCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mCallback.onRouteChanged(route));
+ }
+ }
+
/**
* Interface for receiving events about media routing changes.
*/
public static class Callback {
+ //TODO: clean up these callbacks
/**
* Called when a route is added.
*/
@@ -369,7 +437,7 @@
void notifyRoutes() {
final List<MediaRoute2Info> routes = mRoutes;
// notify only when bound to media router service.
- //TODO: Correct the condition when control category, default rotue, .. are finalized.
+ //TODO: Correct the condition when control category, default route, .. are finalized.
if (routes.size() > 0) {
mExecutor.execute(() -> mCallback.onRoutesChanged(routes));
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 0b64569..4f2a295 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -234,6 +234,54 @@
}
}
+ /**
+ * Requests a volume change for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+ */
+ public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestSetVolume2Manager(client, route, volume);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
+ /**
+ * Requests an incremental volume update for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param delta The delta to add to the current volume.
+ */
+ public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestUpdateVolume2Manager(client, route, delta);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
int findProviderIndex(MediaRoute2ProviderInfo provider) {
final int count = mProviders.size();
for (int i = 0; i < count; i++) {
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 771628c..ca96c9a 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -17,106 +17,14 @@
package android.media;
import android.annotation.UnsupportedAppUsage;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.drm.DrmManagerClient;
-import android.graphics.BitmapFactory;
-import android.mtp.MtpConstants;
import android.net.Uri;
import android.os.Build;
-import android.os.Environment;
import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio;
-import android.provider.MediaStore.Audio.Playlists;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Video;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.sax.Element;
-import android.sax.ElementListener;
-import android.sax.RootElement;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Xml;
-
-import dalvik.system.CloseGuard;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Internal service helper that no-one should use directly.
- *
- * The way the scan currently works is:
- * - The Java MediaScannerService creates a MediaScanner (this class), and calls
- * MediaScanner.scanDirectories on it.
- * - scanDirectories() calls the native processDirectory() for each of the specified directories.
- * - the processDirectory() JNI method wraps the provided mediascanner client in a native
- * 'MyMediaScannerClient' class, then calls processDirectory() on the native MediaScanner
- * object (which got created when the Java MediaScanner was created).
- * - native MediaScanner.processDirectory() calls
- * doProcessDirectory(), which recurses over the folder, and calls
- * native MyMediaScannerClient.scanFile() for every file whose extension matches.
- * - native MyMediaScannerClient.scanFile() calls back on Java MediaScannerClient.scanFile,
- * which calls doScanFile, which after some setup calls back down to native code, calling
- * MediaScanner.processFile().
- * - MediaScanner.processFile() calls one of several methods, depending on the type of the
- * file: parseMP3, parseMP4, parseMidi, parseOgg or parseWMA.
- * - each of these methods gets metadata key/value pairs from the file, and repeatedly
- * calls native MyMediaScannerClient.handleStringTag, which calls back up to its Java
- * counterparts in this file.
- * - Java handleStringTag() gathers the key/value pairs that it's interested in.
- * - once processFile returns and we're back in Java code in doScanFile(), it calls
- * Java MyMediaScannerClient.endFile(), which takes all the data that's been
- * gathered and inserts an entry in to the database.
- *
- * In summary:
- * Java MediaScannerService calls
- * Java MediaScanner scanDirectories, which calls
- * Java MediaScanner processDirectory (native method), which calls
- * native MediaScanner processDirectory, which calls
- * native MyMediaScannerClient scanFile, which calls
- * Java MyMediaScannerClient scanFile, which calls
- * Java MediaScannerClient doScanFile, which calls
- * Java MediaScanner processFile (native method), which calls
- * native MediaScanner processFile, which calls
- * native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls
- * native MyMediaScanner handleStringTag, which calls
- * Java MyMediaScanner handleStringTag.
- * Once MediaScanner processFile returns, an entry is inserted in to the database.
- *
- * The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner.
- *
- * {@hide}
- *
+ * @hide
* @deprecated this media scanner has served faithfully for many years, but it's
* become tedious to test and maintain, mainly due to the way it
* weaves obscurely between managed and native code. It's been
@@ -125,1876 +33,182 @@
*/
@Deprecated
public class MediaScanner implements AutoCloseable {
- static {
- System.loadLibrary("media_jni");
- native_init();
- }
-
- private final static String TAG = "MediaScanner";
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private static final String[] FILES_PRESCAN_PROJECTION = new String[] {
- Files.FileColumns._ID, // 0
- Files.FileColumns.DATA, // 1
- Files.FileColumns.FORMAT, // 2
- Files.FileColumns.DATE_MODIFIED, // 3
- Files.FileColumns.MEDIA_TYPE, // 4
};
- private static final String[] ID_PROJECTION = new String[] {
- Files.FileColumns._ID,
- };
-
- private static final int FILES_PRESCAN_ID_COLUMN_INDEX = 0;
- private static final int FILES_PRESCAN_PATH_COLUMN_INDEX = 1;
- private static final int FILES_PRESCAN_FORMAT_COLUMN_INDEX = 2;
- private static final int FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 3;
- private static final int FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX = 4;
-
- private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] {
- Audio.Playlists.Members.PLAYLIST_ID, // 0
- };
-
- private static final int ID_PLAYLISTS_COLUMN_INDEX = 0;
- private static final int PATH_PLAYLISTS_COLUMN_INDEX = 1;
- private static final int DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX = 2;
-
- private static final String RINGTONES_DIR = "/ringtones/";
- private static final String NOTIFICATIONS_DIR = "/notifications/";
- private static final String ALARMS_DIR = "/alarms/";
- private static final String MUSIC_DIR = "/music/";
- private static final String PODCASTS_DIR = "/podcasts/";
- private static final String AUDIOBOOKS_DIR = "/audiobooks/";
-
- public static final String SCANNED_BUILD_PREFS_NAME = "MediaScanBuild";
- public static final String LAST_INTERNAL_SCAN_FINGERPRINT = "lastScanFingerprint";
- private static final String SYSTEM_SOUNDS_DIR = Environment.getRootDirectory() + "/media/audio";
- private static final String OEM_SOUNDS_DIR = Environment.getOemDirectory() + "/media/audio";
- private static final String PRODUCT_SOUNDS_DIR = Environment.getProductDirectory() + "/media/audio";
- private static String sLastInternalScanFingerprint;
-
- private static final String[] ID3_GENRES = {
- // ID3v1 Genres
- "Blues",
- "Classic Rock",
- "Country",
- "Dance",
- "Disco",
- "Funk",
- "Grunge",
- "Hip-Hop",
- "Jazz",
- "Metal",
- "New Age",
- "Oldies",
- "Other",
- "Pop",
- "R&B",
- "Rap",
- "Reggae",
- "Rock",
- "Techno",
- "Industrial",
- "Alternative",
- "Ska",
- "Death Metal",
- "Pranks",
- "Soundtrack",
- "Euro-Techno",
- "Ambient",
- "Trip-Hop",
- "Vocal",
- "Jazz+Funk",
- "Fusion",
- "Trance",
- "Classical",
- "Instrumental",
- "Acid",
- "House",
- "Game",
- "Sound Clip",
- "Gospel",
- "Noise",
- "AlternRock",
- "Bass",
- "Soul",
- "Punk",
- "Space",
- "Meditative",
- "Instrumental Pop",
- "Instrumental Rock",
- "Ethnic",
- "Gothic",
- "Darkwave",
- "Techno-Industrial",
- "Electronic",
- "Pop-Folk",
- "Eurodance",
- "Dream",
- "Southern Rock",
- "Comedy",
- "Cult",
- "Gangsta",
- "Top 40",
- "Christian Rap",
- "Pop/Funk",
- "Jungle",
- "Native American",
- "Cabaret",
- "New Wave",
- "Psychadelic",
- "Rave",
- "Showtunes",
- "Trailer",
- "Lo-Fi",
- "Tribal",
- "Acid Punk",
- "Acid Jazz",
- "Polka",
- "Retro",
- "Musical",
- "Rock & Roll",
- "Hard Rock",
- // The following genres are Winamp extensions
- "Folk",
- "Folk-Rock",
- "National Folk",
- "Swing",
- "Fast Fusion",
- "Bebob",
- "Latin",
- "Revival",
- "Celtic",
- "Bluegrass",
- "Avantgarde",
- "Gothic Rock",
- "Progressive Rock",
- "Psychedelic Rock",
- "Symphonic Rock",
- "Slow Rock",
- "Big Band",
- "Chorus",
- "Easy Listening",
- "Acoustic",
- "Humour",
- "Speech",
- "Chanson",
- "Opera",
- "Chamber Music",
- "Sonata",
- "Symphony",
- "Booty Bass",
- "Primus",
- "Porn Groove",
- "Satire",
- "Slow Jam",
- "Club",
- "Tango",
- "Samba",
- "Folklore",
- "Ballad",
- "Power Ballad",
- "Rhythmic Soul",
- "Freestyle",
- "Duet",
- "Punk Rock",
- "Drum Solo",
- "A capella",
- "Euro-House",
- "Dance Hall",
- // The following ones seem to be fairly widely supported as well
- "Goa",
- "Drum & Bass",
- "Club-House",
- "Hardcore",
- "Terror",
- "Indie",
- "Britpop",
- null,
- "Polsk Punk",
- "Beat",
- "Christian Gangsta",
- "Heavy Metal",
- "Black Metal",
- "Crossover",
- "Contemporary Christian",
- "Christian Rock",
- "Merengue",
- "Salsa",
- "Thrash Metal",
- "Anime",
- "JPop",
- "Synthpop",
- // 148 and up don't seem to have been defined yet.
- };
-
- private long mNativeContext;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final Context mContext;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final String mPackageName;
- private final String mVolumeName;
- private final ContentProviderClient mMediaProvider;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final Uri mAudioUri;
- private final Uri mVideoUri;
- private final Uri mImagesUri;
- private final Uri mPlaylistsUri;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final Uri mFilesUri;
- private final Uri mFilesFullUri;
- private final boolean mProcessPlaylists;
- private final boolean mProcessGenres;
- private int mMtpObjectHandle;
- private final AtomicBoolean mClosed = new AtomicBoolean();
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
- /** whether to use bulk inserts or individual inserts for each item */
- private static final boolean ENABLE_BULK_INSERTS = true;
-
- // used when scanning the image database so we know whether we have to prune
- // old thumbnail files
- private int mOriginalCount;
- /** Whether the scanner has set a default sound for the ringer ringtone. */
- private boolean mDefaultRingtoneSet;
- /** Whether the scanner has set a default sound for the notification ringtone. */
- private boolean mDefaultNotificationSet;
- /** Whether the scanner has set a default sound for the alarm ringtone. */
- private boolean mDefaultAlarmSet;
- /** The filename for the default sound for the ringer ringtone. */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mDefaultRingtoneFilename;
- /** The filename for the default sound for the notification ringtone. */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mDefaultNotificationFilename;
- /** The filename for the default sound for the alarm ringtone. */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mDefaultAlarmAlertFilename;
- /**
- * The prefix for system properties that define the default sound for
- * ringtones. Concatenate the name of the setting from Settings
- * to get the full system property.
- */
- private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
-
- private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static class FileEntry {
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
long mRowId;
- String mPath;
- long mLastModified;
- int mFormat;
- int mMediaType;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
boolean mLastModifiedChanged;
- /** @deprecated kept intact for lame apps using reflection */
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
FileEntry(long rowId, String path, long lastModified, int format) {
- this(rowId, path, lastModified, format, FileColumns.MEDIA_TYPE_NONE);
- }
-
- FileEntry(long rowId, String path, long lastModified, int format, int mediaType) {
- mRowId = rowId;
- mPath = path;
- mLastModified = lastModified;
- mFormat = format;
- mMediaType = mediaType;
- mLastModifiedChanged = false;
- }
-
- @Override
- public String toString() {
- return mPath + " mRowId: " + mRowId;
+ throw new UnsupportedOperationException();
}
}
- private static class PlaylistEntry {
- String path;
- long bestmatchid;
- int bestmatchlevel;
- }
-
- private final ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<>();
- private final ArrayList<FileEntry> mPlayLists = new ArrayList<>();
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private MediaInserter mMediaInserter;
- private DrmManagerClient mDrmManagerClient = null;
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public MediaScanner(Context c, String volumeName) {
- native_setup();
- mContext = c;
- mPackageName = c.getPackageName();
- mVolumeName = volumeName;
-
- mBitmapOptions.inSampleSize = 1;
- mBitmapOptions.inJustDecodeBounds = true;
-
- setDefaultRingtoneFileNames();
-
- mMediaProvider = mContext.getContentResolver()
- .acquireContentProviderClient(MediaStore.AUTHORITY);
-
- if (sLastInternalScanFingerprint == null) {
- final SharedPreferences scanSettings =
- mContext.getSharedPreferences(SCANNED_BUILD_PREFS_NAME, Context.MODE_PRIVATE);
- sLastInternalScanFingerprint =
- scanSettings.getString(LAST_INTERNAL_SCAN_FINGERPRINT, new String());
- }
-
- mAudioUri = Audio.Media.getContentUri(volumeName);
- mVideoUri = Video.Media.getContentUri(volumeName);
- mImagesUri = Images.Media.getContentUri(volumeName);
- mFilesUri = Files.getContentUri(volumeName);
-
- Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
- filesFullUri = MediaStore.setIncludePending(filesFullUri);
- filesFullUri = MediaStore.setIncludeTrashed(filesFullUri);
- mFilesFullUri = filesFullUri;
-
- if (!volumeName.equals("internal")) {
- // we only support playlists on external media
- mProcessPlaylists = true;
- mProcessGenres = true;
- mPlaylistsUri = Playlists.getContentUri(volumeName);
- } else {
- mProcessPlaylists = false;
- mProcessGenres = false;
- mPlaylistsUri = null;
- }
-
- final Locale locale = mContext.getResources().getConfiguration().locale;
- if (locale != null) {
- String language = locale.getLanguage();
- String country = locale.getCountry();
- if (language != null) {
- if (country != null) {
- setLocale(language + "_" + country);
- } else {
- setLocale(language);
- }
- }
- }
-
- mCloseGuard.open("close");
+ throw new UnsupportedOperationException();
}
- private void setDefaultRingtoneFileNames() {
- mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
- + Settings.System.RINGTONE);
- mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
- + Settings.System.NOTIFICATION_SOUND);
- mDefaultAlarmAlertFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
- + Settings.System.ALARM_ALERT);
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final MyMediaScannerClient mClient = new MyMediaScannerClient();
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private boolean isDrmEnabled() {
- String prop = SystemProperties.get("drm.service.enabled");
- return prop != null && prop.equals("true");
+ throw new UnsupportedOperationException();
}
private class MyMediaScannerClient implements MediaScannerClient {
-
- private final SimpleDateFormat mDateFormatter;
-
- private String mArtist;
- private String mAlbumArtist; // use this if mArtist is missing
- private String mAlbum;
- private String mTitle;
- private String mComposer;
- private String mGenre;
- @UnsupportedAppUsage
- private String mMimeType;
- /** @deprecated file types no longer exist */
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+ private String mMimeType;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private int mFileType;
- private int mTrack;
- private int mYear;
- private int mDuration;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mPath;
- private long mDate;
- private long mLastModified;
- private long mFileSize;
- private String mWriter;
- private int mCompilation;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private boolean mIsDrm;
- @UnsupportedAppUsage
- private boolean mNoMedia; // flag to suppress file from appearing in media tables
- private boolean mScanSuccess;
- private int mWidth;
- private int mHeight;
- private int mColorStandard;
- private int mColorTransfer;
- private int mColorRange;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+ private boolean mNoMedia;
public MyMediaScannerClient() {
- mDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
- mDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ throw new UnsupportedOperationException();
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public FileEntry beginFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean noMedia) {
- mMimeType = mimeType;
- mFileSize = fileSize;
- mIsDrm = false;
- mScanSuccess = true;
-
- if (!isDirectory) {
- if (!noMedia && isNoMediaFile(path)) {
- noMedia = true;
- }
- mNoMedia = noMedia;
-
- // if mimeType was not specified, compute file type based on file extension.
- if (mMimeType == null) {
- mMimeType = MediaFile.getMimeTypeForFile(path);
- }
-
- if (isDrmEnabled() && MediaFile.isDrmMimeType(mMimeType)) {
- getMimeTypeFromDrm(path);
- }
- }
-
- FileEntry entry = makeEntryFor(path);
- // add some slack to avoid a rounding error
- long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
- boolean wasModified = delta > 1 || delta < -1;
- if (entry == null || wasModified) {
- if (wasModified) {
- entry.mLastModified = lastModified;
- } else {
- entry = new FileEntry(0, path, lastModified,
- (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0),
- FileColumns.MEDIA_TYPE_NONE);
- }
- entry.mLastModifiedChanged = true;
- }
-
- if (mProcessPlaylists && MediaFile.isPlayListMimeType(mMimeType)) {
- mPlayLists.add(entry);
- // we don't process playlists in the main scan, so return null
- return null;
- }
-
- // clear all the metadata
- mArtist = null;
- mAlbumArtist = null;
- mAlbum = null;
- mTitle = null;
- mComposer = null;
- mGenre = null;
- mTrack = 0;
- mYear = 0;
- mDuration = 0;
- mPath = path;
- mDate = 0;
- mLastModified = lastModified;
- mWriter = null;
- mCompilation = 0;
- mWidth = 0;
- mHeight = 0;
- mColorStandard = -1;
- mColorTransfer = -1;
- mColorRange = -1;
-
- return entry;
+ throw new UnsupportedOperationException();
}
- @Override
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public void scanFile(String path, long lastModified, long fileSize,
boolean isDirectory, boolean noMedia) {
- // This is the callback funtion from native codes.
- // Log.v(TAG, "scanFile: "+path);
- doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
+ throw new UnsupportedOperationException();
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public Uri doScanFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
- Uri result = null;
-// long t1 = System.currentTimeMillis();
- try {
- FileEntry entry = beginFile(path, mimeType, lastModified,
- fileSize, isDirectory, noMedia);
-
- if (entry == null) {
- return null;
- }
-
- // if this file was just inserted via mtp, set the rowid to zero
- // (even though it already exists in the database), to trigger
- // the correct code path for updating its entry
- if (mMtpObjectHandle != 0) {
- entry.mRowId = 0;
- }
-
- if (entry.mPath != null) {
- if (((!mDefaultNotificationSet &&
- doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
- || (!mDefaultRingtoneSet &&
- doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
- || (!mDefaultAlarmSet &&
- doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)))) {
- Log.w(TAG, "forcing rescan of " + entry.mPath +
- "since ringtone setting didn't finish");
- scanAlways = true;
- } else if (isSystemSoundWithMetadata(entry.mPath)
- && !Build.FINGERPRINT.equals(sLastInternalScanFingerprint)) {
- // file is located on the system partition where the date cannot be trusted:
- // rescan if the build fingerprint has changed since the last scan.
- Log.i(TAG, "forcing rescan of " + entry.mPath
- + " since build fingerprint changed");
- scanAlways = true;
- }
- }
-
- // rescan for metadata if file was modified since last scan
- if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
- if (noMedia) {
- result = endFile(entry, false, false, false, false, false, false);
- } else {
- boolean isaudio = MediaFile.isAudioMimeType(mMimeType);
- boolean isvideo = MediaFile.isVideoMimeType(mMimeType);
- boolean isimage = MediaFile.isImageMimeType(mMimeType);
-
- if (isaudio || isvideo || isimage) {
- path = Environment.maybeTranslateEmulatedPathToInternal(new File(path))
- .getAbsolutePath();
- }
-
- // we only extract metadata for audio and video files
- if (isaudio || isvideo) {
- mScanSuccess = processFile(path, mimeType, this);
- }
-
- if (isimage) {
- mScanSuccess = processImageFile(path);
- }
-
- String lowpath = path.toLowerCase(Locale.ROOT);
- boolean ringtones = mScanSuccess && (lowpath.indexOf(RINGTONES_DIR) > 0);
- boolean notifications = mScanSuccess &&
- (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
- boolean alarms = mScanSuccess && (lowpath.indexOf(ALARMS_DIR) > 0);
- boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCASTS_DIR) > 0);
- boolean audiobooks = mScanSuccess && (lowpath.indexOf(AUDIOBOOKS_DIR) > 0);
- boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) ||
- (!ringtones && !notifications && !alarms && !podcasts && !audiobooks));
-
- result = endFile(entry, ringtones, notifications, alarms, podcasts,
- audiobooks, music);
- }
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
- }
-// long t2 = System.currentTimeMillis();
-// Log.v(TAG, "scanFile: " + path + " took " + (t2-t1));
- return result;
+ throw new UnsupportedOperationException();
}
- private long parseDate(String date) {
- try {
- return mDateFormatter.parse(date).getTime();
- } catch (ParseException e) {
- return 0;
- }
- }
-
- private int parseSubstring(String s, int start, int defaultValue) {
- int length = s.length();
- if (start == length) return defaultValue;
-
- char ch = s.charAt(start++);
- // return defaultValue if we have no integer at all
- if (ch < '0' || ch > '9') return defaultValue;
-
- int result = ch - '0';
- while (start < length) {
- ch = s.charAt(start++);
- if (ch < '0' || ch > '9') return result;
- result = result * 10 + (ch - '0');
- }
-
- return result;
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public void handleStringTag(String name, String value) {
- if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
- // Don't trim() here, to preserve the special \001 character
- // used to force sorting. The media provider will trim() before
- // inserting the title in to the database.
- mTitle = value;
- } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {
- mArtist = value.trim();
- } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")
- || name.equalsIgnoreCase("band") || name.startsWith("band;")) {
- mAlbumArtist = value.trim();
- } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
- mAlbum = value.trim();
- } else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {
- mComposer = value.trim();
- } else if (mProcessGenres &&
- (name.equalsIgnoreCase("genre") || name.startsWith("genre;"))) {
- mGenre = getGenreName(value);
- } else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {
- mYear = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {
- // track number might be of the form "2/12"
- // we just read the number before the slash
- int num = parseSubstring(value, 0, 0);
- mTrack = (mTrack / 1000) * 1000 + num;
- } else if (name.equalsIgnoreCase("discnumber") ||
- name.equals("set") || name.startsWith("set;")) {
- // set number might be of the form "1/3"
- // we just read the number before the slash
- int num = parseSubstring(value, 0, 0);
- mTrack = (num * 1000) + (mTrack % 1000);
- } else if (name.equalsIgnoreCase("duration")) {
- mDuration = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {
- mWriter = value.trim();
- } else if (name.equalsIgnoreCase("compilation")) {
- mCompilation = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("isdrm")) {
- mIsDrm = (parseSubstring(value, 0, 0) == 1);
- } else if (name.equalsIgnoreCase("date")) {
- mDate = parseDate(value);
- } else if (name.equalsIgnoreCase("width")) {
- mWidth = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("height")) {
- mHeight = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("colorstandard")) {
- mColorStandard = parseSubstring(value, 0, -1);
- } else if (name.equalsIgnoreCase("colortransfer")) {
- mColorTransfer = parseSubstring(value, 0, -1);
- } else if (name.equalsIgnoreCase("colorrange")) {
- mColorRange = parseSubstring(value, 0, -1);
- } else {
- //Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")");
- }
+ throw new UnsupportedOperationException();
}
- private boolean convertGenreCode(String input, String expected) {
- String output = getGenreName(input);
- if (output.equals(expected)) {
- return true;
- } else {
- Log.d(TAG, "'" + input + "' -> '" + output + "', expected '" + expected + "'");
- return false;
- }
- }
- private void testGenreNameConverter() {
- convertGenreCode("2", "Country");
- convertGenreCode("(2)", "Country");
- convertGenreCode("(2", "(2");
- convertGenreCode("2 Foo", "Country");
- convertGenreCode("(2) Foo", "Country");
- convertGenreCode("(2 Foo", "(2 Foo");
- convertGenreCode("2Foo", "2Foo");
- convertGenreCode("(2)Foo", "Country");
- convertGenreCode("200 Foo", "Foo");
- convertGenreCode("(200) Foo", "Foo");
- convertGenreCode("200Foo", "200Foo");
- convertGenreCode("(200)Foo", "Foo");
- convertGenreCode("200)Foo", "200)Foo");
- convertGenreCode("200) Foo", "200) Foo");
- }
-
- public String getGenreName(String genreTagValue) {
-
- if (genreTagValue == null) {
- return null;
- }
- final int length = genreTagValue.length();
-
- if (length > 0) {
- boolean parenthesized = false;
- StringBuffer number = new StringBuffer();
- int i = 0;
- for (; i < length; ++i) {
- char c = genreTagValue.charAt(i);
- if (i == 0 && c == '(') {
- parenthesized = true;
- } else if (Character.isDigit(c)) {
- number.append(c);
- } else {
- break;
- }
- }
- char charAfterNumber = i < length ? genreTagValue.charAt(i) : ' ';
- if ((parenthesized && charAfterNumber == ')')
- || !parenthesized && Character.isWhitespace(charAfterNumber)) {
- try {
- short genreIndex = Short.parseShort(number.toString());
- if (genreIndex >= 0) {
- if (genreIndex < ID3_GENRES.length && ID3_GENRES[genreIndex] != null) {
- return ID3_GENRES[genreIndex];
- } else if (genreIndex == 0xFF) {
- return null;
- } else if (genreIndex < 0xFF && (i + 1) < length) {
- // genre is valid but unknown,
- // if there is a string after the value we take it
- if (parenthesized && charAfterNumber == ')') {
- i++;
- }
- String ret = genreTagValue.substring(i).trim();
- if (ret.length() != 0) {
- return ret;
- }
- } else {
- // else return the number, without parentheses
- return number.toString();
- }
- }
- } catch (NumberFormatException e) {
- }
- }
- }
-
- return genreTagValue;
- }
-
- private boolean processImageFile(String path) {
- try {
- mBitmapOptions.outWidth = 0;
- mBitmapOptions.outHeight = 0;
- BitmapFactory.decodeFile(path, mBitmapOptions);
- mWidth = mBitmapOptions.outWidth;
- mHeight = mBitmapOptions.outHeight;
- return mWidth > 0 && mHeight > 0;
- } catch (Throwable th) {
- // ignore;
- }
- return false;
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public void setMimeType(String mimeType) {
- if ("audio/mp4".equals(mMimeType) &&
- mimeType.startsWith("video")) {
- // for feature parity with Donut, we force m4a files to keep the
- // audio/mp4 mimetype, even if they are really "enhanced podcasts"
- // with a video track
- return;
- }
- mMimeType = mimeType;
+ throw new UnsupportedOperationException();
}
- /**
- * Formats the data into a values array suitable for use with the Media
- * Content Provider.
- *
- * @return a map of values
- */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private ContentValues toValues() {
- ContentValues map = new ContentValues();
-
- map.put(MediaStore.MediaColumns.DATA, mPath);
- map.put(MediaStore.MediaColumns.TITLE, mTitle);
- map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
- map.put(MediaStore.MediaColumns.SIZE, mFileSize);
- map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
- map.put(MediaStore.MediaColumns.IS_DRM, mIsDrm);
- map.putNull(MediaStore.MediaColumns.HASH);
-
- String resolution = null;
- if (mWidth > 0 && mHeight > 0) {
- map.put(MediaStore.MediaColumns.WIDTH, mWidth);
- map.put(MediaStore.MediaColumns.HEIGHT, mHeight);
- resolution = mWidth + "x" + mHeight;
- }
-
- if (!mNoMedia) {
- if (MediaFile.isVideoMimeType(mMimeType)) {
- map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0
- ? mArtist : MediaStore.UNKNOWN_STRING));
- map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0
- ? mAlbum : MediaStore.UNKNOWN_STRING));
- map.put(Video.Media.DURATION, mDuration);
- if (resolution != null) {
- map.put(Video.Media.RESOLUTION, resolution);
- }
- if (mColorStandard >= 0) {
- map.put(Video.Media.COLOR_STANDARD, mColorStandard);
- }
- if (mColorTransfer >= 0) {
- map.put(Video.Media.COLOR_TRANSFER, mColorTransfer);
- }
- if (mColorRange >= 0) {
- map.put(Video.Media.COLOR_RANGE, mColorRange);
- }
- if (mDate > 0) {
- map.put(Video.Media.DATE_TAKEN, mDate);
- }
- } else if (MediaFile.isImageMimeType(mMimeType)) {
- // FIXME - add DESCRIPTION
- } else if (MediaFile.isAudioMimeType(mMimeType)) {
- map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
- mArtist : MediaStore.UNKNOWN_STRING);
- map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
- mAlbumArtist.length() > 0) ? mAlbumArtist : null);
- map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
- mAlbum : MediaStore.UNKNOWN_STRING);
- map.put(Audio.Media.COMPOSER, mComposer);
- map.put(Audio.Media.GENRE, mGenre);
- if (mYear != 0) {
- map.put(Audio.Media.YEAR, mYear);
- }
- map.put(Audio.Media.TRACK, mTrack);
- map.put(Audio.Media.DURATION, mDuration);
- map.put(Audio.Media.COMPILATION, mCompilation);
- }
- }
- return map;
+ throw new UnsupportedOperationException();
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,
boolean alarms, boolean podcasts, boolean audiobooks, boolean music)
throws RemoteException {
- // update database
-
- // use album artist if artist is missing
- if (mArtist == null || mArtist.length() == 0) {
- mArtist = mAlbumArtist;
- }
-
- ContentValues values = toValues();
- String title = values.getAsString(MediaStore.MediaColumns.TITLE);
- if (title == null || TextUtils.isEmpty(title.trim())) {
- title = MediaFile.getFileTitle(values.getAsString(MediaStore.MediaColumns.DATA));
- values.put(MediaStore.MediaColumns.TITLE, title);
- }
- String album = values.getAsString(Audio.Media.ALBUM);
- if (MediaStore.UNKNOWN_STRING.equals(album)) {
- album = values.getAsString(MediaStore.MediaColumns.DATA);
- // extract last path segment before file name
- int lastSlash = album.lastIndexOf('/');
- if (lastSlash >= 0) {
- int previousSlash = 0;
- while (true) {
- int idx = album.indexOf('/', previousSlash + 1);
- if (idx < 0 || idx >= lastSlash) {
- break;
- }
- previousSlash = idx;
- }
- if (previousSlash != 0) {
- album = album.substring(previousSlash + 1, lastSlash);
- values.put(Audio.Media.ALBUM, album);
- }
- }
- }
- long rowId = entry.mRowId;
- if (MediaFile.isAudioMimeType(mMimeType) && (rowId == 0 || mMtpObjectHandle != 0)) {
- // Only set these for new entries. For existing entries, they
- // may have been modified later, and we want to keep the current
- // values so that custom ringtones still show up in the ringtone
- // picker.
- values.put(Audio.Media.IS_RINGTONE, ringtones);
- values.put(Audio.Media.IS_NOTIFICATION, notifications);
- values.put(Audio.Media.IS_ALARM, alarms);
- values.put(Audio.Media.IS_MUSIC, music);
- values.put(Audio.Media.IS_PODCAST, podcasts);
- values.put(Audio.Media.IS_AUDIOBOOK, audiobooks);
- } else if (MediaFile.isExifMimeType(mMimeType) && !mNoMedia) {
- ExifInterface exif = null;
- try {
- exif = new ExifInterface(entry.mPath);
- } catch (Exception ex) {
- // exif is null
- }
- if (exif != null) {
- long time = exif.getGpsDateTime();
- if (time != -1) {
- values.put(Images.Media.DATE_TAKEN, time);
- } else {
- // If no time zone information is available, we should consider using
- // EXIF local time as taken time if the difference between file time
- // and EXIF local time is not less than 1 Day, otherwise MediaProvider
- // will use file time as taken time.
- time = exif.getDateTime();
- if (time != -1 && Math.abs(mLastModified * 1000 - time) >= 86400000) {
- values.put(Images.Media.DATE_TAKEN, time);
- }
- }
-
- int orientation = exif.getAttributeInt(
- ExifInterface.TAG_ORIENTATION, -1);
- if (orientation != -1) {
- // We only recognize a subset of orientation tag values.
- int degree;
- switch(orientation) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- degree = 90;
- break;
- case ExifInterface.ORIENTATION_ROTATE_180:
- degree = 180;
- break;
- case ExifInterface.ORIENTATION_ROTATE_270:
- degree = 270;
- break;
- default:
- degree = 0;
- break;
- }
- values.put(Images.Media.ORIENTATION, degree);
- }
- }
- }
-
- Uri tableUri = mFilesUri;
- int mediaType = FileColumns.MEDIA_TYPE_NONE;
- MediaInserter inserter = mMediaInserter;
- if (!mNoMedia) {
- if (MediaFile.isVideoMimeType(mMimeType)) {
- tableUri = mVideoUri;
- mediaType = FileColumns.MEDIA_TYPE_VIDEO;
- } else if (MediaFile.isImageMimeType(mMimeType)) {
- tableUri = mImagesUri;
- mediaType = FileColumns.MEDIA_TYPE_IMAGE;
- } else if (MediaFile.isAudioMimeType(mMimeType)) {
- tableUri = mAudioUri;
- mediaType = FileColumns.MEDIA_TYPE_AUDIO;
- } else if (MediaFile.isPlayListMimeType(mMimeType)) {
- tableUri = mPlaylistsUri;
- mediaType = FileColumns.MEDIA_TYPE_PLAYLIST;
- }
- }
- Uri result = null;
- boolean needToSetSettings = false;
- // Setting a flag in order not to use bulk insert for the file related with
- // notifications, ringtones, and alarms, because the rowId of the inserted file is
- // needed.
- if (notifications && !mDefaultNotificationSet) {
- if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
- doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
- needToSetSettings = true;
- }
- } else if (ringtones && !mDefaultRingtoneSet) {
- if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
- doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
- needToSetSettings = true;
- }
- } else if (alarms && !mDefaultAlarmSet) {
- if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
- doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
- needToSetSettings = true;
- }
- }
-
- if (rowId == 0) {
- if (mMtpObjectHandle != 0) {
- values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
- }
- if (tableUri == mFilesUri) {
- int format = entry.mFormat;
- if (format == 0) {
- format = MediaFile.getFormatCode(entry.mPath, mMimeType);
- }
- values.put(Files.FileColumns.FORMAT, format);
- }
- // New file, insert it.
- // Directories need to be inserted before the files they contain, so they
- // get priority when bulk inserting.
- // If the rowId of the inserted file is needed, it gets inserted immediately,
- // bypassing the bulk inserter.
- if (inserter == null || needToSetSettings) {
- if (inserter != null) {
- inserter.flushAll();
- }
- result = mMediaProvider.insert(tableUri, values);
- } else if (entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) {
- inserter.insertwithPriority(tableUri, values);
- } else {
- inserter.insert(tableUri, values);
- }
-
- if (result != null) {
- rowId = ContentUris.parseId(result);
- entry.mRowId = rowId;
- }
- } else {
- // updated file
- result = ContentUris.withAppendedId(tableUri, rowId);
- // path should never change, and we want to avoid replacing mixed cased paths
- // with squashed lower case paths
- values.remove(MediaStore.MediaColumns.DATA);
-
- if (!mNoMedia) {
- // Changing media type must be done as separate update
- if (mediaType != entry.mMediaType) {
- final ContentValues mediaTypeValues = new ContentValues();
- mediaTypeValues.put(FileColumns.MEDIA_TYPE, mediaType);
- mMediaProvider.update(ContentUris.withAppendedId(mFilesUri, rowId),
- mediaTypeValues, null, null);
- }
- }
-
- mMediaProvider.update(result, values, null, null);
- }
-
- if(needToSetSettings) {
- if (notifications) {
- setRingtoneIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId);
- mDefaultNotificationSet = true;
- } else if (ringtones) {
- setRingtoneIfNotSet(Settings.System.RINGTONE, tableUri, rowId);
- mDefaultRingtoneSet = true;
- } else if (alarms) {
- setRingtoneIfNotSet(Settings.System.ALARM_ALERT, tableUri, rowId);
- mDefaultAlarmSet = true;
- }
- }
-
- return result;
+ throw new UnsupportedOperationException();
}
- private boolean doesPathHaveFilename(String path, String filename) {
- int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
- int filenameLength = filename.length();
- return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
- pathFilenameStart + filenameLength == path.length();
- }
-
- private void setRingtoneIfNotSet(String settingName, Uri uri, long rowId) {
- if (wasRingtoneAlreadySet(settingName)) {
- return;
- }
-
- ContentResolver cr = mContext.getContentResolver();
- String existingSettingValue = Settings.System.getString(cr, settingName);
- if (TextUtils.isEmpty(existingSettingValue)) {
- final Uri settingUri = Settings.System.getUriFor(settingName);
- final Uri ringtoneUri = ContentUris.withAppendedId(uri, rowId);
- RingtoneManager.setActualDefaultRingtoneUri(mContext,
- RingtoneManager.getDefaultType(settingUri), ringtoneUri);
- }
- Settings.System.putInt(cr, settingSetIndicatorName(settingName), 1);
- }
-
- /** @deprecated file types no longer exist */
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private int getFileTypeFromDrm(String path) {
- return 0;
- }
-
- private void getMimeTypeFromDrm(String path) {
- mMimeType = null;
-
- if (mDrmManagerClient == null) {
- mDrmManagerClient = new DrmManagerClient(mContext);
- }
-
- if (mDrmManagerClient.canHandle(path, null)) {
- mIsDrm = true;
- mMimeType = mDrmManagerClient.getOriginalMimeType(path);
- }
-
- if (mMimeType == null) {
- mMimeType = ContentResolver.MIME_TYPE_DEFAULT;
- }
- }
-
- }; // end of anonymous MediaScannerClient instance
-
- private static boolean isSystemSoundWithMetadata(String path) {
- if (path.startsWith(SYSTEM_SOUNDS_DIR + ALARMS_DIR)
- || path.startsWith(SYSTEM_SOUNDS_DIR + RINGTONES_DIR)
- || path.startsWith(SYSTEM_SOUNDS_DIR + NOTIFICATIONS_DIR)
- || path.startsWith(OEM_SOUNDS_DIR + ALARMS_DIR)
- || path.startsWith(OEM_SOUNDS_DIR + RINGTONES_DIR)
- || path.startsWith(OEM_SOUNDS_DIR + NOTIFICATIONS_DIR)
- || path.startsWith(PRODUCT_SOUNDS_DIR + ALARMS_DIR)
- || path.startsWith(PRODUCT_SOUNDS_DIR + RINGTONES_DIR)
- || path.startsWith(PRODUCT_SOUNDS_DIR + NOTIFICATIONS_DIR)) {
- return true;
- }
- return false;
- }
-
- private String settingSetIndicatorName(String base) {
- return base + "_set";
- }
-
- private boolean wasRingtoneAlreadySet(String name) {
- ContentResolver cr = mContext.getContentResolver();
- String indicatorName = settingSetIndicatorName(name);
- try {
- return Settings.System.getInt(cr, indicatorName) != 0;
- } catch (SettingNotFoundException e) {
- return false;
+ throw new UnsupportedOperationException();
}
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private void prescan(String filePath, boolean prescanFiles) throws RemoteException {
- Cursor c = null;
- String where = null;
- String[] selectionArgs = null;
-
- mPlayLists.clear();
-
- if (filePath != null) {
- // query for only one file
- where = MediaStore.Files.FileColumns._ID + ">?" +
- " AND " + Files.FileColumns.DATA + "=?";
- selectionArgs = new String[] { "", filePath };
- } else {
- where = MediaStore.Files.FileColumns._ID + ">?";
- selectionArgs = new String[] { "" };
- }
-
- mDefaultRingtoneSet = wasRingtoneAlreadySet(Settings.System.RINGTONE);
- mDefaultNotificationSet = wasRingtoneAlreadySet(Settings.System.NOTIFICATION_SOUND);
- mDefaultAlarmSet = wasRingtoneAlreadySet(Settings.System.ALARM_ALERT);
-
- // Tell the provider to not delete the file.
- // If the file is truly gone the delete is unnecessary, and we want to avoid
- // accidentally deleting files that are really there (this may happen if the
- // filesystem is mounted and unmounted while the scanner is running).
- Uri.Builder builder = mFilesUri.buildUpon();
- builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
- MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
-
- // Build the list of files from the content provider
- try {
- if (prescanFiles) {
- // First read existing files from the files table.
- // Because we'll be deleting entries for missing files as we go,
- // we need to query the database in small batches, to avoid problems
- // with CursorWindow positioning.
- long lastId = Long.MIN_VALUE;
- Uri limitUri = mFilesUri.buildUpon()
- .appendQueryParameter(MediaStore.PARAM_LIMIT, "1000").build();
-
- while (true) {
- selectionArgs[0] = "" + lastId;
- if (c != null) {
- c.close();
- c = null;
- }
- c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
- where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
- if (c == null) {
- break;
- }
-
- int num = c.getCount();
-
- if (num == 0) {
- break;
- }
- while (c.moveToNext()) {
- long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
- String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
- int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
- long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
- lastId = rowId;
-
- // Only consider entries with absolute path names.
- // This allows storing URIs in the database without the
- // media scanner removing them.
- if (path != null && path.startsWith("/")) {
- boolean exists = false;
- try {
- exists = Os.access(path, android.system.OsConstants.F_OK);
- } catch (ErrnoException e1) {
- }
- if (!exists && !MtpConstants.isAbstractObject(format)) {
- // do not delete missing playlists, since they may have been
- // modified by the user.
- // The user can delete them in the media player instead.
- // instead, clear the path and lastModified fields in the row
- String mimeType = MediaFile.getMimeTypeForFile(path);
- if (!MediaFile.isPlayListMimeType(mimeType)) {
- deleter.delete(rowId);
- if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
- deleter.flush();
- String parent = new File(path).getParent();
- mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null);
- }
- }
- }
- }
- }
- }
- }
- }
- finally {
- if (c != null) {
- c.close();
- }
- deleter.flush();
- }
-
- // compute original size of images
- mOriginalCount = 0;
- c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null, null);
- if (c != null) {
- mOriginalCount = c.getCount();
- c.close();
- }
+ throw new UnsupportedOperationException();
}
- static class MediaBulkDeleter {
- StringBuilder whereClause = new StringBuilder();
- ArrayList<String> whereArgs = new ArrayList<String>(100);
- final ContentProviderClient mProvider;
- final Uri mBaseUri;
-
- public MediaBulkDeleter(ContentProviderClient provider, Uri baseUri) {
- mProvider = provider;
- mBaseUri = baseUri;
- }
-
- public void delete(long id) throws RemoteException {
- if (whereClause.length() != 0) {
- whereClause.append(",");
- }
- whereClause.append("?");
- whereArgs.add("" + id);
- if (whereArgs.size() > 100) {
- flush();
- }
- }
- public void flush() throws RemoteException {
- int size = whereArgs.size();
- if (size > 0) {
- String [] foo = new String [size];
- foo = whereArgs.toArray(foo);
- int numrows = mProvider.delete(mBaseUri,
- MediaStore.MediaColumns._ID + " IN (" +
- whereClause.toString() + ")", foo);
- //Log.i("@@@@@@@@@", "rows deleted: " + numrows);
- whereClause.setLength(0);
- whereArgs.clear();
- }
- }
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private void postscan(final String[] directories) throws RemoteException {
-
- // handle playlists last, after we know what media files are on the storage.
- if (mProcessPlaylists) {
- processPlayLists();
- }
-
- // allow GC to clean up
- mPlayLists.clear();
+ throw new UnsupportedOperationException();
}
- private void releaseResources() {
- // release the DrmManagerClient resources
- if (mDrmManagerClient != null) {
- mDrmManagerClient.close();
- mDrmManagerClient = null;
- }
- }
-
- public void scanDirectories(String[] directories) {
- try {
- long start = System.currentTimeMillis();
- prescan(null, true);
- long prescan = System.currentTimeMillis();
-
- if (ENABLE_BULK_INSERTS) {
- // create MediaInserter for bulk inserts
- mMediaInserter = new MediaInserter(mMediaProvider, 500);
- }
-
- for (int i = 0; i < directories.length; i++) {
- processDirectory(directories[i], mClient);
- }
-
- if (ENABLE_BULK_INSERTS) {
- // flush remaining inserts
- mMediaInserter.flushAll();
- mMediaInserter = null;
- }
-
- long scan = System.currentTimeMillis();
- postscan(directories);
- long end = System.currentTimeMillis();
-
- if (false) {
- Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
- Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n");
- Log.d(TAG, "postscan time: " + (end - scan) + "ms\n");
- Log.d(TAG, " total time: " + (end - start) + "ms\n");
- }
- } catch (SQLException e) {
- // this might happen if the SD card is removed while the media scanner is running
- Log.e(TAG, "SQLException in MediaScanner.scan()", e);
- } catch (UnsupportedOperationException e) {
- // this might happen if the SD card is removed while the media scanner is running
- Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
- } finally {
- releaseResources();
- }
- }
-
- // this function is used to scan a single file
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public Uri scanSingleFile(String path, String mimeType) {
- try {
- prescan(path, true);
-
- File file = new File(path);
- if (!file.exists() || !file.canRead()) {
- return null;
- }
-
- // lastModified is in milliseconds on Files.
- long lastModifiedSeconds = file.lastModified() / 1000;
-
- // always scan the file, so we can return the content://media Uri for existing files
- return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
- false, true, MediaScanner.isNoMediaPath(path));
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
- return null;
- } finally {
- releaseResources();
- }
+ throw new UnsupportedOperationException();
}
- private static boolean isNoMediaFile(String path) {
- File file = new File(path);
- if (file.isDirectory()) return false;
-
- // special case certain file names
- // I use regionMatches() instead of substring() below
- // to avoid memory allocation
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
- // ignore those ._* files created by MacOS
- if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
- return true;
- }
-
- // ignore album art files created by Windows Media Player:
- // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
- // and AlbumArt_{...}_Small.jpg
- if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
- if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
- path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
- return true;
- }
- int length = path.length() - lastSlash - 1;
- if ((length == 17 && path.regionMatches(
- true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
- (length == 10
- && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
- return true;
- }
- }
- }
- return false;
- }
-
- private static HashMap<String,String> mNoMediaPaths = new HashMap<String,String>();
- private static HashMap<String,String> mMediaPaths = new HashMap<String,String>();
-
- /* MediaProvider calls this when a .nomedia file is added or removed */
- public static void clearMediaPathCache(boolean clearMediaPaths, boolean clearNoMediaPaths) {
- synchronized (MediaScanner.class) {
- if (clearMediaPaths) {
- mMediaPaths.clear();
- }
- if (clearNoMediaPaths) {
- mNoMediaPaths.clear();
- }
- }
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public static boolean isNoMediaPath(String path) {
- if (path == null) {
- return false;
- }
- // return true if file or any parent directory has name starting with a dot
- if (path.indexOf("/.") >= 0) {
- return true;
- }
-
- int firstSlash = path.lastIndexOf('/');
- if (firstSlash <= 0) {
- return false;
- }
- String parent = path.substring(0, firstSlash);
-
- synchronized (MediaScanner.class) {
- if (mNoMediaPaths.containsKey(parent)) {
- return true;
- } else if (!mMediaPaths.containsKey(parent)) {
- // check to see if any parent directories have a ".nomedia" file
- // start from 1 so we don't bother checking in the root directory
- int offset = 1;
- while (offset >= 0) {
- int slashIndex = path.indexOf('/', offset);
- if (slashIndex > offset) {
- slashIndex++; // move past slash
- File file = new File(path.substring(0, slashIndex) + ".nomedia");
- if (file.exists()) {
- // we have a .nomedia in one of the parent directories
- mNoMediaPaths.put(parent, "");
- return true;
- }
- }
- offset = slashIndex;
- }
- mMediaPaths.put(parent, "");
- }
- }
-
- return isNoMediaFile(path);
+ throw new UnsupportedOperationException();
}
- public void scanMtpFile(String path, int objectHandle, int format) {
- String mimeType = MediaFile.getMimeType(path, format);
- File file = new File(path);
- long lastModifiedSeconds = file.lastModified() / 1000;
-
- if (!MediaFile.isAudioMimeType(mimeType) && !MediaFile.isVideoMimeType(mimeType) &&
- !MediaFile.isImageMimeType(mimeType) && !MediaFile.isPlayListMimeType(mimeType) &&
- !MediaFile.isDrmMimeType(mimeType)) {
-
- // no need to use the media scanner, but we need to update last modified and file size
- ContentValues values = new ContentValues();
- values.put(Files.FileColumns.SIZE, file.length());
- values.put(Files.FileColumns.DATE_MODIFIED, lastModifiedSeconds);
- try {
- String[] whereArgs = new String[] { Integer.toString(objectHandle) };
- mMediaProvider.update(Files.getMtpObjectsUri(mVolumeName), values,
- "_id=?", whereArgs);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in scanMtpFile", e);
- }
- return;
- }
-
- mMtpObjectHandle = objectHandle;
- Cursor fileList = null;
- try {
- if (MediaFile.isPlayListMimeType(mimeType)) {
- // build file cache so we can look up tracks in the playlist
- prescan(null, true);
-
- FileEntry entry = makeEntryFor(path);
- if (entry != null) {
- fileList = mMediaProvider.query(mFilesUri,
- FILES_PRESCAN_PROJECTION, null, null, null, null);
- processPlayList(entry, fileList);
- }
- } else {
- // MTP will create a file entry for us so we don't want to do it in prescan
- prescan(path, false);
-
- // always scan the file, so we can return the content://media Uri for existing files
- mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
- (format == MtpConstants.FORMAT_ASSOCIATION), true, isNoMediaPath(path));
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
- } finally {
- mMtpObjectHandle = 0;
- if (fileList != null) {
- fileList.close();
- }
- releaseResources();
- }
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
FileEntry makeEntryFor(String path) {
- String where;
- String[] selectionArgs;
-
- Cursor c = null;
- try {
- where = Files.FileColumns.DATA + "=?";
- selectionArgs = new String[] { path };
- c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION,
- where, selectionArgs, null, null);
- if (c != null && c.moveToFirst()) {
- long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
- long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
- int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
- int mediaType = c.getInt(FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX);
- return new FileEntry(rowId, path, lastModified, format, mediaType);
- }
- } catch (RemoteException e) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return null;
+ throw new UnsupportedOperationException();
}
- // returns the number of matching file/directory names, starting from the right
- private int matchPaths(String path1, String path2) {
- int result = 0;
- int end1 = path1.length();
- int end2 = path2.length();
-
- while (end1 > 0 && end2 > 0) {
- int slash1 = path1.lastIndexOf('/', end1 - 1);
- int slash2 = path2.lastIndexOf('/', end2 - 1);
- int backSlash1 = path1.lastIndexOf('\\', end1 - 1);
- int backSlash2 = path2.lastIndexOf('\\', end2 - 1);
- int start1 = (slash1 > backSlash1 ? slash1 : backSlash1);
- int start2 = (slash2 > backSlash2 ? slash2 : backSlash2);
- if (start1 < 0) start1 = 0; else start1++;
- if (start2 < 0) start2 = 0; else start2++;
- int length = end1 - start1;
- if (end2 - start2 != length) break;
- if (path1.regionMatches(true, start1, path2, start2, length)) {
- result++;
- end1 = start1 - 1;
- end2 = start2 - 1;
- } else break;
- }
-
- return result;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+ private void setLocale(String locale) {
+ throw new UnsupportedOperationException();
}
- private boolean matchEntries(long rowId, String data) {
-
- int len = mPlaylistEntries.size();
- boolean done = true;
- for (int i = 0; i < len; i++) {
- PlaylistEntry entry = mPlaylistEntries.get(i);
- if (entry.bestmatchlevel == Integer.MAX_VALUE) {
- continue; // this entry has been matched already
- }
- done = false;
- if (data.equalsIgnoreCase(entry.path)) {
- entry.bestmatchid = rowId;
- entry.bestmatchlevel = Integer.MAX_VALUE;
- continue; // no need for path matching
- }
-
- int matchLength = matchPaths(data, entry.path);
- if (matchLength > entry.bestmatchlevel) {
- entry.bestmatchid = rowId;
- entry.bestmatchlevel = matchLength;
- }
- }
- return done;
- }
-
- private void cachePlaylistEntry(String line, String playListDirectory) {
- PlaylistEntry entry = new PlaylistEntry();
- // watch for trailing whitespace
- int entryLength = line.length();
- while (entryLength > 0 && Character.isWhitespace(line.charAt(entryLength - 1))) entryLength--;
- // path should be longer than 3 characters.
- // avoid index out of bounds errors below by returning here.
- if (entryLength < 3) return;
- if (entryLength < line.length()) line = line.substring(0, entryLength);
-
- // does entry appear to be an absolute path?
- // look for Unix or DOS absolute paths
- char ch1 = line.charAt(0);
- boolean fullPath = (ch1 == '/' ||
- (Character.isLetter(ch1) && line.charAt(1) == ':' && line.charAt(2) == '\\'));
- // if we have a relative path, combine entry with playListDirectory
- if (!fullPath)
- line = playListDirectory + line;
- entry.path = line;
- //FIXME - should we look for "../" within the path?
-
- mPlaylistEntries.add(entry);
- }
-
- private void processCachedPlaylist(Cursor fileList, ContentValues values, Uri playlistUri) {
- fileList.moveToPosition(-1);
- while (fileList.moveToNext()) {
- long rowId = fileList.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
- String data = fileList.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
- if (matchEntries(rowId, data)) {
- break;
- }
- }
-
- int len = mPlaylistEntries.size();
- int index = 0;
- for (int i = 0; i < len; i++) {
- PlaylistEntry entry = mPlaylistEntries.get(i);
- if (entry.bestmatchlevel > 0) {
- try {
- values.clear();
- values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
- values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(entry.bestmatchid));
- mMediaProvider.insert(playlistUri, values);
- index++;
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.processCachedPlaylist()", e);
- return;
- }
- }
- }
- mPlaylistEntries.clear();
- }
-
- private void processM3uPlayList(String path, String playListDirectory, Uri uri,
- ContentValues values, Cursor fileList) {
- BufferedReader reader = null;
- try {
- File f = new File(path);
- if (f.exists()) {
- reader = new BufferedReader(
- new InputStreamReader(new FileInputStream(f)), 8192);
- String line = reader.readLine();
- mPlaylistEntries.clear();
- while (line != null) {
- // ignore comment lines, which begin with '#'
- if (line.length() > 0 && line.charAt(0) != '#') {
- cachePlaylistEntry(line, playListDirectory);
- }
- line = reader.readLine();
- }
-
- processCachedPlaylist(fileList, values, uri);
- }
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
- } finally {
- try {
- if (reader != null)
- reader.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
- }
- }
- }
-
- private void processPlsPlayList(String path, String playListDirectory, Uri uri,
- ContentValues values, Cursor fileList) {
- BufferedReader reader = null;
- try {
- File f = new File(path);
- if (f.exists()) {
- reader = new BufferedReader(
- new InputStreamReader(new FileInputStream(f)), 8192);
- String line = reader.readLine();
- mPlaylistEntries.clear();
- while (line != null) {
- // ignore comment lines, which begin with '#'
- if (line.startsWith("File")) {
- int equals = line.indexOf('=');
- if (equals > 0) {
- cachePlaylistEntry(line.substring(equals + 1), playListDirectory);
- }
- }
- line = reader.readLine();
- }
-
- processCachedPlaylist(fileList, values, uri);
- }
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
- } finally {
- try {
- if (reader != null)
- reader.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
- }
- }
- }
-
- class WplHandler implements ElementListener {
-
- final ContentHandler handler;
- String playListDirectory;
-
- public WplHandler(String playListDirectory, Uri uri, Cursor fileList) {
- this.playListDirectory = playListDirectory;
-
- RootElement root = new RootElement("smil");
- Element body = root.getChild("body");
- Element seq = body.getChild("seq");
- Element media = seq.getChild("media");
- media.setElementListener(this);
-
- this.handler = root.getContentHandler();
- }
-
- @Override
- public void start(Attributes attributes) {
- String path = attributes.getValue("", "src");
- if (path != null) {
- cachePlaylistEntry(path, playListDirectory);
- }
- }
-
- @Override
- public void end() {
- }
-
- ContentHandler getContentHandler() {
- return handler;
- }
- }
-
- private void processWplPlayList(String path, String playListDirectory, Uri uri,
- ContentValues values, Cursor fileList) {
- FileInputStream fis = null;
- try {
- File f = new File(path);
- if (f.exists()) {
- fis = new FileInputStream(f);
-
- mPlaylistEntries.clear();
- Xml.parse(fis, Xml.findEncodingByName("UTF-8"),
- new WplHandler(playListDirectory, uri, fileList).getContentHandler());
-
- processCachedPlaylist(fileList, values, uri);
- }
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if (fis != null)
- fis.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processWplPlayList()", e);
- }
- }
- }
-
- private void processPlayList(FileEntry entry, Cursor fileList) throws RemoteException {
- String path = entry.mPath;
- ContentValues values = new ContentValues();
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash < 0) throw new IllegalArgumentException("bad path " + path);
- Uri uri, membersUri;
- long rowId = entry.mRowId;
-
- // make sure we have a name
- String name = values.getAsString(MediaStore.Audio.Playlists.NAME);
- if (name == null) {
- name = values.getAsString(MediaStore.MediaColumns.TITLE);
- if (name == null) {
- // extract name from file name
- int lastDot = path.lastIndexOf('.');
- name = (lastDot < 0 ? path.substring(lastSlash + 1)
- : path.substring(lastSlash + 1, lastDot));
- }
- }
-
- values.put(MediaStore.Audio.Playlists.NAME, name);
- values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
-
- if (rowId == 0) {
- values.put(MediaStore.Audio.Playlists.DATA, path);
- uri = mMediaProvider.insert(mPlaylistsUri, values);
- rowId = ContentUris.parseId(uri);
- membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
- } else {
- uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
- mMediaProvider.update(uri, values, null, null);
-
- // delete members of existing playlist
- membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
- mMediaProvider.delete(membersUri, null, null);
- }
-
- String playListDirectory = path.substring(0, lastSlash + 1);
- String mimeType = MediaFile.getMimeTypeForFile(path);
- switch (mimeType) {
- case "application/vnd.ms-wpl":
- processWplPlayList(path, playListDirectory, membersUri, values, fileList);
- break;
- case "audio/x-mpegurl":
- processM3uPlayList(path, playListDirectory, membersUri, values, fileList);
- break;
- case "audio/x-scpls":
- processPlsPlayList(path, playListDirectory, membersUri, values, fileList);
- break;
- }
- }
-
- private void processPlayLists() throws RemoteException {
- Iterator<FileEntry> iterator = mPlayLists.iterator();
- Cursor fileList = null;
- try {
- // use the files uri and projection because we need the format column,
- // but restrict the query to just audio files
- fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
- "media_type=2", null, null, null);
- while (iterator.hasNext()) {
- FileEntry entry = iterator.next();
- // only process playlist files if they are new or have been modified since the last scan
- if (entry.mLastModifiedChanged) {
- processPlayList(entry, fileList);
- }
- }
- } catch (RemoteException e1) {
- } finally {
- if (fileList != null) {
- fileList.close();
- }
- }
- }
-
- private native void processDirectory(String path, MediaScannerClient client);
- private native boolean processFile(String path, String mimeType, MediaScannerClient client);
- @UnsupportedAppUsage
- private native void setLocale(String locale);
-
- public native byte[] extractAlbumArt(FileDescriptor fd);
-
- private static native final void native_init();
- private native final void native_setup();
- private native final void native_finalize();
-
@Override
public void close() {
- mCloseGuard.close();
- if (mClosed.compareAndSet(false, true)) {
- mMediaProvider.close();
- native_finalize();
- }
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
-
- close();
- } finally {
- super.finalize();
- }
+ throw new UnsupportedOperationException();
}
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 435d8d7..ff40442 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -846,7 +846,7 @@
* Adds an audio file to the list of ringtones.
*
* After making sure the given file is an audio file, copies the file to the ringtone storage,
- * and asks the {@link android.media.MediaScanner} to scan that file. This call will block until
+ * and asks the system to scan that file. This call will block until
* the scan is completed.
*
* The directory where the copied file is stored is the directory that matches the ringtone's
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index fb581b5..a315c1e 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -139,6 +139,12 @@
/**
* Create a thumbnail for given audio file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The audio file.
* @param size The desired thumbnail size.
@@ -231,6 +237,12 @@
/**
* Create a thumbnail for given image file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The audio file.
* @param size The desired thumbnail size.
@@ -334,6 +346,12 @@
/**
* Create a thumbnail for given video file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The video file.
* @param size The desired thumbnail size.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 45ee210..bd9ea13 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -17,7 +17,6 @@
"android_media_MediaPlayer.cpp",
"android_media_MediaProfiles.cpp",
"android_media_MediaRecorder.cpp",
- "android_media_MediaScanner.cpp",
"android_media_MediaSync.cpp",
"android_media_ResampleInputStream.cpp",
"android_media_Streams.cpp",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index d24edc7..94299bc 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1446,7 +1446,6 @@
extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
extern int register_android_media_MediaMuxer(JNIEnv *env);
extern int register_android_media_MediaRecorder(JNIEnv *env);
-extern int register_android_media_MediaScanner(JNIEnv *env);
extern int register_android_media_MediaSync(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
@@ -1485,11 +1484,6 @@
goto bail;
}
- if (register_android_media_MediaScanner(env) < 0) {
- ALOGE("ERROR: MediaScanner native registration failed\n");
- goto bail;
- }
-
if (register_android_media_MediaMetadataRetriever(env) < 0) {
ALOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
deleted file mode 100644
index 58044c0..0000000
--- a/media/jni/android_media_MediaScanner.cpp
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
-**
-** Copyright 2007, 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaScannerJNI"
-#include <utils/Log.h>
-#include <utils/threads.h>
-#include <media/mediascanner.h>
-#include <media/stagefright/StagefrightMediaScanner.h>
-#include <private/media/VideoFrame.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
-#include <android-base/macros.h> // for FALLTHROUGH_INTENDED
-
-using namespace android;
-
-
-static const char* const kClassMediaScannerClient =
- "android/media/MediaScannerClient";
-
-static const char* const kClassMediaScanner =
- "android/media/MediaScanner";
-
-static const char* const kRunTimeException =
- "java/lang/RuntimeException";
-
-static const char* const kIllegalArgumentException =
- "java/lang/IllegalArgumentException";
-
-struct fields_t {
- jfieldID context;
-};
-static fields_t fields;
-
-static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
- if (env->ExceptionCheck()) {
- ALOGE("An exception was thrown by callback '%s'.", methodName);
- LOGE_EX(env);
- env->ExceptionClear();
- return UNKNOWN_ERROR;
- }
- return OK;
-}
-
-// stolen from dalvik/vm/checkJni.cpp
-static bool isValidUtf8(const char* bytes) {
- while (*bytes != '\0') {
- unsigned char utf8 = *(bytes++);
- // Switch on the high four bits.
- switch (utf8 >> 4) {
- case 0x00:
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- // Bit pattern 0xxx. No need for any extra bytes.
- break;
- case 0x08:
- case 0x09:
- case 0x0a:
- case 0x0b:
- case 0x0f:
- /*
- * Bit pattern 10xx or 1111, which are illegal start bytes.
- * Note: 1111 is valid for normal UTF-8, but not the
- * modified UTF-8 used here.
- */
- return false;
- case 0x0e:
- // Bit pattern 1110, so there are two additional bytes.
- utf8 = *(bytes++);
- if ((utf8 & 0xc0) != 0x80) {
- return false;
- }
- // Fall through to take care of the final byte.
- FALLTHROUGH_INTENDED;
- case 0x0c:
- case 0x0d:
- // Bit pattern 110x, so there is one additional byte.
- utf8 = *(bytes++);
- if ((utf8 & 0xc0) != 0x80) {
- return false;
- }
- break;
- }
- }
- return true;
-}
-
-class MyMediaScannerClient : public MediaScannerClient
-{
-public:
- MyMediaScannerClient(JNIEnv *env, jobject client)
- : mEnv(env),
- mClient(env->NewGlobalRef(client)),
- mScanFileMethodID(0),
- mHandleStringTagMethodID(0),
- mSetMimeTypeMethodID(0)
- {
- ALOGV("MyMediaScannerClient constructor");
- jclass mediaScannerClientInterface =
- env->FindClass(kClassMediaScannerClient);
-
- if (mediaScannerClientInterface == NULL) {
- ALOGE("Class %s not found", kClassMediaScannerClient);
- } else {
- mScanFileMethodID = env->GetMethodID(
- mediaScannerClientInterface,
- "scanFile",
- "(Ljava/lang/String;JJZZ)V");
-
- mHandleStringTagMethodID = env->GetMethodID(
- mediaScannerClientInterface,
- "handleStringTag",
- "(Ljava/lang/String;Ljava/lang/String;)V");
-
- mSetMimeTypeMethodID = env->GetMethodID(
- mediaScannerClientInterface,
- "setMimeType",
- "(Ljava/lang/String;)V");
- }
- }
-
- virtual ~MyMediaScannerClient()
- {
- ALOGV("MyMediaScannerClient destructor");
- mEnv->DeleteGlobalRef(mClient);
- }
-
- virtual status_t scanFile(const char* path, long long lastModified,
- long long fileSize, bool isDirectory, bool noMedia)
- {
- ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
- path, lastModified, fileSize, isDirectory);
-
- jstring pathStr;
- if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
-
- mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
- fileSize, isDirectory, noMedia);
-
- mEnv->DeleteLocalRef(pathStr);
- return checkAndClearExceptionFromCallback(mEnv, "scanFile");
- }
-
- virtual status_t handleStringTag(const char* name, const char* value)
- {
- ALOGV("handleStringTag: name(%s) and value(%s)", name, value);
- jstring nameStr, valueStr;
- if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
- char *cleaned = NULL;
- if (!isValidUtf8(value)) {
- cleaned = strdup(value);
- char *chp = cleaned;
- char ch;
- while ((ch = *chp)) {
- if (ch & 0x80) {
- *chp = '?';
- }
- chp++;
- }
- value = cleaned;
- }
- valueStr = mEnv->NewStringUTF(value);
- free(cleaned);
- if (valueStr == NULL) {
- mEnv->DeleteLocalRef(nameStr);
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
-
- mEnv->CallVoidMethod(
- mClient, mHandleStringTagMethodID, nameStr, valueStr);
-
- mEnv->DeleteLocalRef(nameStr);
- mEnv->DeleteLocalRef(valueStr);
- return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
- }
-
- virtual status_t setMimeType(const char* mimeType)
- {
- ALOGV("setMimeType: %s", mimeType);
- jstring mimeTypeStr;
- if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) {
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
-
- mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr);
-
- mEnv->DeleteLocalRef(mimeTypeStr);
- return checkAndClearExceptionFromCallback(mEnv, "setMimeType");
- }
-
-private:
- JNIEnv *mEnv;
- jobject mClient;
- jmethodID mScanFileMethodID;
- jmethodID mHandleStringTagMethodID;
- jmethodID mSetMimeTypeMethodID;
-};
-
-
-static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
-{
- return (MediaScanner *) env->GetLongField(thiz, fields.context);
-}
-
-static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
-{
- env->SetLongField(thiz, fields.context, (jlong)s);
-}
-
-static void
-android_media_MediaScanner_processDirectory(
- JNIEnv *env, jobject thiz, jstring path, jobject client)
-{
- ALOGV("processDirectory");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return;
- }
-
- if (path == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return;
- }
-
- const char *pathStr = env->GetStringUTFChars(path, NULL);
- if (pathStr == NULL) { // Out of memory
- return;
- }
-
- MyMediaScannerClient myClient(env, client);
- MediaScanResult result = mp->processDirectory(pathStr, myClient);
- if (result == MEDIA_SCAN_RESULT_ERROR) {
- ALOGE("An error occurred while scanning directory '%s'.", pathStr);
- }
- env->ReleaseStringUTFChars(path, pathStr);
-}
-
-static jboolean
-android_media_MediaScanner_processFile(
- JNIEnv *env, jobject thiz, jstring path,
- jstring mimeType, jobject client)
-{
- ALOGV("processFile");
-
- // Lock already hold by processDirectory
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return false;
- }
-
- if (path == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return false;
- }
-
- const char *pathStr = env->GetStringUTFChars(path, NULL);
- if (pathStr == NULL) { // Out of memory
- return false;
- }
-
- const char *mimeTypeStr =
- (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
- if (mimeType && mimeTypeStr == NULL) { // Out of memory
- // ReleaseStringUTFChars can be called with an exception pending.
- env->ReleaseStringUTFChars(path, pathStr);
- return false;
- }
-
- MyMediaScannerClient myClient(env, client);
- MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
- if (result == MEDIA_SCAN_RESULT_ERROR) {
- ALOGE("An error occurred while scanning file '%s'.", pathStr);
- }
- env->ReleaseStringUTFChars(path, pathStr);
- if (mimeType) {
- env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
- }
- return result != MEDIA_SCAN_RESULT_ERROR;
-}
-
-static void
-android_media_MediaScanner_setLocale(
- JNIEnv *env, jobject thiz, jstring locale)
-{
- ALOGV("setLocale");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return;
- }
-
- if (locale == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return;
- }
- const char *localeStr = env->GetStringUTFChars(locale, NULL);
- if (localeStr == NULL) { // Out of memory
- return;
- }
- mp->setLocale(localeStr);
-
- env->ReleaseStringUTFChars(locale, localeStr);
-}
-
-static jbyteArray
-android_media_MediaScanner_extractAlbumArt(
- JNIEnv *env, jobject thiz, jobject fileDescriptor)
-{
- ALOGV("extractAlbumArt");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return NULL;
- }
-
- if (fileDescriptor == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return NULL;
- }
-
- int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
- MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd);
- if (mediaAlbumArt == NULL) {
- return NULL;
- }
-
- jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
- if (array != NULL) {
- const jbyte* data =
- reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
- env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
- }
-
- free(mediaAlbumArt);
- // if NewByteArray() returned NULL, an out-of-memory
- // exception will have been raised. I just want to
- // return null in that case.
- env->ExceptionClear();
- return array;
-}
-
-// This function gets a field ID, which in turn causes class initialization.
-// It is called from a static block in MediaScanner, which won't run until the
-// first time an instance of this class is used.
-static void
-android_media_MediaScanner_native_init(JNIEnv *env)
-{
- ALOGV("native_init");
- jclass clazz = env->FindClass(kClassMediaScanner);
- if (clazz == NULL) {
- return;
- }
-
- fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
- if (fields.context == NULL) {
- return;
- }
-}
-
-static void
-android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
-{
- ALOGV("native_setup");
- MediaScanner *mp = new StagefrightMediaScanner;
-
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "Out of memory");
- return;
- }
-
- env->SetLongField(thiz, fields.context, (jlong)mp);
-}
-
-static void
-android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("native_finalize");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == 0) {
- return;
- }
- delete mp;
- setNativeScanner_l(env, thiz, 0);
-}
-
-static const JNINativeMethod gMethods[] = {
- {
- "processDirectory",
- "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
- (void *)android_media_MediaScanner_processDirectory
- },
-
- {
- "processFile",
- "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z",
- (void *)android_media_MediaScanner_processFile
- },
-
- {
- "setLocale",
- "(Ljava/lang/String;)V",
- (void *)android_media_MediaScanner_setLocale
- },
-
- {
- "extractAlbumArt",
- "(Ljava/io/FileDescriptor;)[B",
- (void *)android_media_MediaScanner_extractAlbumArt
- },
-
- {
- "native_init",
- "()V",
- (void *)android_media_MediaScanner_native_init
- },
-
- {
- "native_setup",
- "()V",
- (void *)android_media_MediaScanner_native_setup
- },
-
- {
- "native_finalize",
- "()V",
- (void *)android_media_MediaScanner_native_finalize
- },
-};
-
-// This function only registers the native methods, and is called from
-// JNI_OnLoad in android_media_MediaPlayer.cpp
-int register_android_media_MediaScanner(JNIEnv *env)
-{
- return AndroidRuntime::registerNativeMethods(env,
- kClassMediaScanner, gMethods, NELEM(gMethods));
-}
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 8d39a93..680c879 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -130,6 +130,33 @@
}
}
+ @Override
+ public void onSetVolume(String routeId, int volume) {
+ MediaRoute2Info route = mRoutes.get(routeId);
+ if (route == null) {
+ return;
+ }
+ volume = Math.min(volume, Math.max(0, route.getVolumeMax()));
+ mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+ .setVolume(volume)
+ .build());
+ publishRoutes();
+ }
+
+ @Override
+ public void onUpdateVolume(String routeId, int delta) {
+ MediaRoute2Info route = mRoutes.get(routeId);
+ if (route == null) {
+ return;
+ }
+ int volume = route.getVolume() + delta;
+ volume = Math.min(volume, Math.max(0, route.getVolumeMax()));
+ mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+ .setVolume(volume)
+ .build());
+ publishRoutes();
+ }
+
void publishRoutes() {
MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
.addRoutes(mRoutes.values())
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index da832ac..ca43d04 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -47,8 +47,10 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -190,17 +192,8 @@
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
mManager.registerCallback(mExecutor, mockCallback);
- MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
-
- mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
- mRouter2.registerCallback(mExecutor, mockRouterCallback);
- mRouter2.unregisterCallback(mockRouterCallback);
-
- verify(mockCallback, timeout(TIMEOUT_MS))
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
Map<String, MediaRoute2Info> routes =
- createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ waitAndGetRoutesWithManager(CONTROL_CATEGORIES_SPECIAL);
Assert.assertEquals(1, routes.size());
Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
@@ -214,12 +207,10 @@
@Test
public void testGetRoutes() throws Exception {
MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
-
- mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
mRouter2.registerCallback(mExecutor, mockCallback);
- verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
- Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes());
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_SPECIAL);
+
Assert.assertEquals(1, routes.size());
Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
@@ -228,51 +219,40 @@
@Test
public void testOnRouteSelected() throws Exception {
- MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
+ MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
mManager.registerCallback(mExecutor, managerCallback);
- mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
- mRouter2.registerCallback(mExecutor, mockRouterCallback);
+ mRouter2.registerCallback(mExecutor, routerCallback);
- verify(managerCallback, timeout(TIMEOUT_MS))
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
- Map<String, MediaRoute2Info> routes =
- createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
- mManager.selectRoute(mPackageName, routeToSelect);
-
assertNotNull(routeToSelect);
+
+ mManager.selectRoute(mPackageName, routeToSelect);
verify(managerCallback, timeout(TIMEOUT_MS))
.onRouteAdded(argThat(route -> route.equals(routeToSelect)));
+ mRouter2.unregisterCallback(routerCallback);
mManager.unregisterCallback(managerCallback);
- mRouter2.unregisterCallback(mockRouterCallback);
}
/**
* Tests selecting and unselecting routes of a single provider.
*/
@Test
- public void testSingleProviderSelect() {
+ public void testSingleProviderSelect() throws Exception {
MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);
mManager.registerCallback(mExecutor, managerCallback);
- mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
mRouter2.registerCallback(mExecutor, routerCallback);
- verify(managerCallback, timeout(TIMEOUT_MS))
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
- Map<String, MediaRoute2Info> routes =
- createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
- verify(managerCallback, timeout(TIMEOUT_MS)
- )
+ verify(managerCallback, timeout(TIMEOUT_MS))
.onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID1, routeInfo.getId())
&& TextUtils.equals(routeInfo.getClientPackageName(), mPackageName)));
@@ -291,14 +271,67 @@
}
@Test
- public void testVolumeHandling() {
+ public void testControlVolumeWithRouter() throws Exception {
MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
- mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_ALL);
mRouter2.registerCallback(mExecutor, mockCallback);
+
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+ int originalVolume = volRoute.getVolume();
+ int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+ int targetVolume = originalVolume + deltaVolume;
+
+ mRouter2.requestSetVolume(volRoute, targetVolume);
verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
- Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes());
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == targetVolume));
+
+ mRouter2.requestUpdateVolume(volRoute, -deltaVolume);
+ verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == originalVolume));
+
+ mRouter2.unregisterCallback(mockCallback);
+ }
+
+ @Test
+ public void testControlVolumeWithManager() throws Exception {
+ MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
+ MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
+
+ mManager.registerCallback(mExecutor, managerCallback);
+ mRouter2.registerCallback(mExecutor, mockCallback);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
+
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+ int originalVolume = volRoute.getVolume();
+ int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+ int targetVolume = originalVolume + deltaVolume;
+
+ mManager.requestSetVolume(volRoute, targetVolume);
+ verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == targetVolume));
+
+ mManager.requestUpdateVolume(volRoute, -deltaVolume);
+ verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == originalVolume));
+
+ mRouter2.unregisterCallback(mockCallback);
+ mManager.unregisterCallback(managerCallback);
+ }
+
+ @Test
+ public void testVolumeHandling() throws Exception {
+ MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
+ mRouter2.registerCallback(mExecutor, mockCallback);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_ALL);
MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -310,6 +343,48 @@
mRouter2.unregisterCallback(mockCallback);
}
+ Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> routes) {
+ if (routes.size() > 0) latch.countDown();
+ }
+ };
+ mRouter2.setControlCategories(controlCategories);
+ mRouter2.registerCallback(mExecutor, callback);
+ try {
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return createRouteMap(mRouter2.getRoutes());
+ } finally {
+ mRouter2.unregisterCallback(callback);
+ }
+ }
+
+ Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
+ throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // Dummy callback is required to send control category info.
+ MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
+ MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> routes) {
+ if (routes.size() > 0) latch.countDown();
+ }
+ };
+ mManager.registerCallback(mExecutor, managerCallback);
+ mRouter2.setControlCategories(controlCategories);
+ mRouter2.registerCallback(mExecutor, routerCallback);
+ try {
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ } finally {
+ mRouter2.unregisterCallback(routerCallback);
+ mManager.unregisterCallback(managerCallback);
+ }
+ }
+
// Helper for getting routes easily
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp
index 342d796..68e937c 100644
--- a/packages/BackupEncryption/Android.bp
+++ b/packages/BackupEncryption/Android.bp
@@ -17,8 +17,7 @@
android_app {
name: "BackupEncryption",
srcs: ["src/**/*.java"],
- libs: ["backup-encryption-protos"],
- static_libs: ["backuplib"],
+ static_libs: ["backup-encryption-protos", "backuplib"],
optimize: { enabled: false },
platform_apis: true,
certificate: "platform",
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java
new file mode 100644
index 0000000..2035b66
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import android.content.Context;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
+
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+class EncryptionKeyHelper {
+ private static SecureRandom sSecureRandom = new SecureRandom();
+
+ private final Context mContext;
+ private final RecoverableKeyStoreSecondaryKeyManager
+ .RecoverableKeyStoreSecondaryKeyManagerProvider
+ mSecondaryKeyManagerProvider;
+
+ EncryptionKeyHelper(Context context) {
+ mContext = context;
+ mSecondaryKeyManagerProvider =
+ () ->
+ new RecoverableKeyStoreSecondaryKeyManager(
+ RecoveryController.getInstance(mContext), sSecureRandom);
+ }
+
+ RecoverableKeyStoreSecondaryKeyManager
+ .RecoverableKeyStoreSecondaryKeyManagerProvider getKeyManagerProvider() {
+ return mSecondaryKeyManagerProvider;
+ }
+
+ RecoverableKeyStoreSecondaryKey getActiveSecondaryKey()
+ throws UnrecoverableKeyException, InternalRecoveryServiceException {
+ String keyAlias = CryptoSettings.getInstance(mContext).getActiveSecondaryKeyAlias().get();
+ return mSecondaryKeyManagerProvider.get().get(keyAlias).get();
+ }
+
+ SecretKey getTertiaryKey(
+ String packageName,
+ RecoverableKeyStoreSecondaryKey secondaryKey)
+ throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
+ NoSuchAlgorithmException, IOException, NoSuchPaddingException,
+ InvalidKeyException {
+ TertiaryKeyManager tertiaryKeyManager =
+ new TertiaryKeyManager(
+ mContext,
+ sSecureRandom,
+ TertiaryKeyRotationScheduler.getInstance(mContext),
+ secondaryKey,
+ packageName);
+ return tertiaryKeyManager.getKey();
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
new file mode 100644
index 0000000..1d841b4
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.KeyWrapUtils;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.backup.encryption.tasks.EncryptedKvBackupTask;
+import com.android.server.backup.encryption.tasks.EncryptedKvRestoreTask;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Map;
+
+public class KeyValueEncrypter {
+ private final Context mContext;
+ private final EncryptionKeyHelper mKeyHelper;
+
+ public KeyValueEncrypter(Context context) {
+ mContext = context;
+ mKeyHelper = new EncryptionKeyHelper(mContext);
+ }
+
+ public void encryptKeyValueData(
+ String packageName, ParcelFileDescriptor inputFd, OutputStream outputStream)
+ throws Exception {
+ EncryptedKvBackupTask.EncryptedKvBackupTaskFactory backupTaskFactory =
+ new EncryptedKvBackupTask.EncryptedKvBackupTaskFactory();
+ EncryptedKvBackupTask backupTask =
+ backupTaskFactory.newInstance(
+ mContext,
+ new SecureRandom(),
+ new FileBackupServer(outputStream),
+ CryptoSettings.getInstance(mContext),
+ mKeyHelper.getKeyManagerProvider(),
+ inputFd,
+ packageName);
+ backupTask.performBackup(/* incremental */ false);
+ }
+
+ public void decryptKeyValueData(String packageName,
+ InputStream encryptedInputStream, ParcelFileDescriptor outputFd) throws Exception {
+ RecoverableKeyStoreSecondaryKey secondaryKey = mKeyHelper.getActiveSecondaryKey();
+
+ EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory restoreTaskFactory =
+ new EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory();
+ EncryptedKvRestoreTask restoreTask =
+ restoreTaskFactory.newInstance(
+ mContext,
+ mKeyHelper.getKeyManagerProvider(),
+ new InputStreamFullRestoreDownloader(encryptedInputStream),
+ secondaryKey.getAlias(),
+ KeyWrapUtils.wrap(
+ secondaryKey.getSecretKey(),
+ mKeyHelper.getTertiaryKey(packageName, secondaryKey)));
+
+ restoreTask.getRestoreData(outputFd);
+ }
+
+ // TODO(b/142455725): Extract into a commong class.
+ private static class FileBackupServer implements CryptoBackupServer {
+ private static final String EMPTY_DOC_ID = "";
+
+ private final OutputStream mOutputStream;
+
+ FileBackupServer(OutputStream outputStream) {
+ mOutputStream = outputStream;
+ }
+
+ @Override
+ public String uploadIncrementalBackup(
+ String packageName,
+ String oldDocId,
+ byte[] diffScript,
+ WrappedKeyProto.WrappedKey tertiaryKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String uploadNonIncrementalBackup(
+ String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
+ try {
+ mOutputStream.write(data);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write encrypted data to file: ", e);
+ }
+
+ return EMPTY_DOC_ID;
+ }
+
+ @Override
+ public void setActiveSecondaryKeyAlias(
+ String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
+ // Do nothing.
+ }
+ }
+
+ // TODO(b/142455725): Extract into a commong class.
+ private static class InputStreamFullRestoreDownloader extends FullRestoreDownloader {
+ private final InputStream mInputStream;
+
+ InputStreamFullRestoreDownloader(InputStream inputStream) {
+ mInputStream = inputStream;
+ }
+
+ @Override
+ public int readNextChunk(byte[] buffer) throws IOException {
+ return mInputStream.read(buffer);
+ }
+
+ @Override
+ public void finish(FinishType finishType) {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Error while reading restore data");
+ }
+ }
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
index 1d0224d..c3cb335 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
@@ -18,27 +18,58 @@
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.encryption.KeyValueEncrypter;
import com.android.server.backup.transport.DelegatingTransport;
import com.android.server.backup.transport.TransportClient;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
* sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
* TransportClient.connect(String)}.
*/
public class IntermediateEncryptingTransport extends DelegatingTransport {
+ private static final String BACKUP_TEMP_DIR = "backup";
+ private static final String RESTORE_TEMP_DIR = "restore";
+
private final TransportClient mTransportClient;
private final Object mConnectLock = new Object();
+ private final Context mContext;
private volatile IBackupTransport mRealTransport;
+ private AtomicReference<String> mNextRestorePackage = new AtomicReference<>();
+ private final KeyValueEncrypter mKeyValueEncrypter;
+ private final boolean mShouldEncrypt;
+
+ IntermediateEncryptingTransport(
+ TransportClient transportClient, Context context, boolean shouldEncrypt) {
+ this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt);
+ }
@VisibleForTesting
- IntermediateEncryptingTransport(TransportClient transportClient) {
+ IntermediateEncryptingTransport(
+ TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
+ boolean shouldEncrypt) {
mTransportClient = transportClient;
+ mContext = context;
+ mKeyValueEncrypter = keyValueEncrypter;
+ mShouldEncrypt = shouldEncrypt;
}
@Override
@@ -46,9 +77,116 @@
if (mRealTransport == null) {
connect();
}
+ Log.d(TAG, "real transport = " + mRealTransport.name());
return mRealTransport;
}
+ @Override
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
+ throws RemoteException {
+ if (!mShouldEncrypt) {
+ return super.performBackup(packageInfo, inFd, flags);
+ }
+
+ File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName);
+ if (encryptedStorageFile == null) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Encrypt the backup data and write it into a temp file.
+ try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) {
+ mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd,
+ encryptedOutput);
+ } catch (Throwable e) {
+ Log.e(TAG, "Failed to encrypt backup data: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Pass the temp file to the real transport for backup.
+ try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) {
+ return super.performBackup(
+ packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read encrypted data from temp storage: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+ }
+
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+ if (!mShouldEncrypt) {
+ return super.getRestoreData(outFd);
+ }
+
+ String nextRestorePackage = mNextRestorePackage.get();
+ if (nextRestorePackage == null) {
+ Log.e(TAG, "No next restore package set");
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage);
+ if (encryptedStorageFile == null) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Get encrypted restore data from the real transport and write it into a temp file.
+ try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) {
+ int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD()));
+ if (status != BackupTransport.TRANSPORT_OK) {
+ Log.e(TAG, "Failed to read restore data from transport, status = " + status);
+ return status;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write encrypted data to temp storage: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Decrypt the data and write it into the fd given by the real transport.
+ try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) {
+ mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd);
+ encryptedStorageFile.delete();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to decrypt restored data: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ return BackupTransport.TRANSPORT_OK;
+ }
+
+ @Override
+ public RestoreDescription nextRestorePackage() throws RemoteException {
+ if (!mShouldEncrypt) {
+ return super.nextRestorePackage();
+ }
+
+ RestoreDescription restoreDescription = super.nextRestorePackage();
+ mNextRestorePackage.set(restoreDescription.getPackageName());
+
+ return restoreDescription;
+ }
+
+ @VisibleForTesting
+ protected File getBackupTempStorage(String packageName) {
+ return getTempStorage(packageName, BACKUP_TEMP_DIR);
+ }
+
+ @VisibleForTesting
+ protected File getRestoreTempStorage(String packageName) {
+ return getTempStorage(packageName, RESTORE_TEMP_DIR);
+ }
+
+ private File getTempStorage(String packageName, String operationType) {
+ File encryptedDir = new File(mContext.getFilesDir(), operationType);
+ encryptedDir.mkdir();
+ File encryptedFile = new File(encryptedDir, packageName);
+ try {
+ encryptedFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to create temp file for encrypted data: ", e);
+ }
+ return encryptedFile;
+ }
+
private void connect() throws RemoteException {
Log.i(TAG, "connecting " + mTransportClient);
synchronized (mConnectLock) {
@@ -65,4 +203,9 @@
TransportClient getClient() {
return mTransportClient;
}
+
+ @VisibleForTesting
+ void setNextRestorePackage(String nextRestorePackage) {
+ mNextRestorePackage.set(nextRestorePackage);
+ }
}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
index 6e6d571..7c4082c 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
@@ -26,20 +26,20 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.backup.transport.TransportClientManager;
import com.android.server.backup.transport.TransportStats;
import java.util.HashMap;
import java.util.Map;
-/**
- * Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances.
- */
+/** Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances. */
public class IntermediateEncryptingTransportManager {
private static final String CALLER = "IntermediateEncryptingTransportManager";
private final TransportClientManager mTransportClientManager;
private final Object mTransportsLock = new Object();
private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
+ private Context mContext;
@VisibleForTesting
IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
@@ -48,6 +48,7 @@
public IntermediateEncryptingTransportManager(Context context) {
this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
+ mContext = context;
}
/**
@@ -55,31 +56,42 @@
* provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
* the real {@link IBackupTransport}.
+ *
* @param intent {@link Intent} created with a call to {@link
- * TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
+ * TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
* @return
*/
public IntermediateEncryptingTransport get(Intent intent) {
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
synchronized (mTransportsLock) {
- return mTransports.computeIfAbsent(transportIntent.getComponent(),
- c -> create(transportIntent));
+ return mTransports.computeIfAbsent(
+ transportIntent.getComponent(), c -> create(transportIntent));
}
}
- /**
- * Create an instance of {@link IntermediateEncryptingTransport}.
- */
+ /** Create an instance of {@link IntermediateEncryptingTransport}. */
private IntermediateEncryptingTransport create(Intent realTransportIntent) {
Log.d(TAG, "create: intent:" + realTransportIntent);
- return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
- realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
+
+ LockPatternUtils patternUtils = new LockPatternUtils(mContext);
+ boolean shouldEncrypt =
+ realTransportIntent.getComponent().getClassName().contains("EncryptedLocalTransport")
+ && (patternUtils.isLockPatternEnabled(UserHandle.myUserId())
+ || patternUtils.isLockPasswordEnabled(UserHandle.myUserId()));
+
+ return new IntermediateEncryptingTransport(
+ mTransportClientManager.getTransportClient(
+ realTransportIntent.getComponent(),
+ realTransportIntent.getExtras(),
+ CALLER),
+ mContext,
+ shouldEncrypt);
}
/**
- * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to
- * {@link #get(Intent)} with this {@link Intent}.
+ * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to {@link
+ * #get(Intent)} with this {@link Intent}.
*/
public void cleanup(Intent intent) {
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
index cc4b0ab..a85b2e4 100644
--- a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
+++ b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
@@ -18,43 +18,71 @@
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupTransport;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.encryption.KeyValueEncrypter;
import com.android.server.backup.transport.TransportClient;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class IntermediateEncryptingTransportTest {
- @Mock private IBackupTransport mRealTransport;
- @Mock private TransportClient mTransportClient;
+ private static final String TEST_PACKAGE_NAME = "test_package";
private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
+ private final PackageInfo mTestPackage = new PackageInfo();
+
+ @Mock private IBackupTransport mRealTransport;
+ @Mock private TransportClient mTransportClient;
+ @Mock private ParcelFileDescriptor mParcelFileDescriptor;
+ @Mock private KeyValueEncrypter mKeyValueEncrypter;
+ @Mock private Context mContext;
+
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ private File mTempFile;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mIntermediateEncryptingTransport = new IntermediateEncryptingTransport(mTransportClient);
+
+ mIntermediateEncryptingTransport =
+ new IntermediateEncryptingTransport(
+ mTransportClient, mContext, mKeyValueEncrypter, true);
+ mTestPackage.packageName = TEST_PACKAGE_NAME;
+ mTempFile = mTemporaryFolder.newFile();
+
+ when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
+ when(mRealTransport.getRestoreData(any())).thenReturn(BackupTransport.TRANSPORT_OK);
}
@Test
public void testGetDelegate_callsConnect() throws Exception {
- when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
-
IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
assertEquals(mRealTransport, ret);
@@ -74,4 +102,79 @@
verify(mTransportClient, times(1)).connect(anyString());
verifyNoMoreInteractions(mTransportClient);
}
+
+ @Test
+ public void testPerformBackup_shouldEncryptTrue_encryptsDataAndPassesToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
+
+ mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
+
+ verify(mKeyValueEncrypter, times(1))
+ .encryptKeyValueData(eq(TEST_PACKAGE_NAME), eq(mParcelFileDescriptor), any());
+ verify(mRealTransport, times(1)).performBackup(eq(mTestPackage), any(), eq(0));
+ }
+
+ @Test
+ public void testPerformBackup_shouldEncryptFalse_doesntEncryptDataAndPassedToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(
+ mTransportClient, mContext, mKeyValueEncrypter, false);
+
+ mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
+
+ verifyZeroInteractions(mKeyValueEncrypter);
+ verify(mRealTransport, times(1))
+ .performBackup(eq(mTestPackage), eq(mParcelFileDescriptor), eq(0));
+ }
+
+ @Test
+ public void testGetRestoreData_shouldEncryptTrue_decryptsDataAndPassesToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
+ mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
+
+ mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
+
+ verify(mKeyValueEncrypter, times(1))
+ .decryptKeyValueData(eq(TEST_PACKAGE_NAME), any(), eq(mParcelFileDescriptor));
+ verify(mRealTransport, times(1)).getRestoreData(any());
+ }
+
+ @Test
+ public void testGetRestoreData_shouldEncryptFalse_doesntDecryptDataAndPassesToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(
+ mTransportClient, mContext, mKeyValueEncrypter, false);
+ mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
+
+ mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
+
+ verifyZeroInteractions(mKeyValueEncrypter);
+ verify(mRealTransport, times(1)).getRestoreData(eq(mParcelFileDescriptor));
+ }
+
+ private final class TestIntermediateTransport extends IntermediateEncryptingTransport {
+ TestIntermediateTransport(
+ TransportClient transportClient,
+ Context context,
+ KeyValueEncrypter keyValueEncrypter,
+ boolean shouldEncrypt) {
+ super(transportClient, context, keyValueEncrypter, shouldEncrypt);
+ }
+
+ @Override
+ protected File getBackupTempStorage(String packageName) {
+ return mTempFile;
+ }
+
+ @Override
+ protected File getRestoreTempStorage(String packageName) {
+ return mTempFile;
+ }
+ }
}
diff --git a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
index 72ec8d8..94f5b96 100644
--- a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
@@ -48,6 +48,18 @@
/>
<com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/grid"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ systemui:intent="intent:#Intent;component=com.android.car.home/.AppGridActivity;end"
+ systemui:longIntent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ android:src="@drawable/car_ic_apps"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingTop="30dp"
+ android:paddingBottom="30dp"
+ />
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -58,6 +70,7 @@
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
+
</LinearLayout>
<LinearLayout
@@ -78,6 +91,7 @@
android:alpha="0.7"
/>
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 897976f..18ae582 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -22,6 +22,8 @@
android:layout_height="match_parent"
android:background="@drawable/system_bar_background"
android:orientation="vertical">
+ <!--The 20dp padding is the difference between the background selected icon size and the ripple
+ that was chosen, thus it's a hack to make it look pretty and not an official margin value-->
<LinearLayout
android:id="@id/nav_buttons"
android:layout_width="match_parent"
@@ -37,7 +39,6 @@
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
- systemui:longIntent="intent:#Intent;action=com.google.android.demandspace.START;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:useMoreIcon="false"
/>
@@ -108,13 +109,11 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <!-- Click handling will be initialized in CarNavigationBarView because its
- id = notifications which is treated special for the opening of the notification panel
- -->
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/notifications"
style="@style/NavigationBarButton"
- android:src="@drawable/car_ic_notification"
+ systemui:icon="@drawable/car_ic_notification"
+ systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"
systemui:selectedIcon="@drawable/car_ic_notification_selected"
systemui:useMoreIcon="false"
/>
@@ -124,13 +123,13 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.statusbar.car.CarFacetButton
+ <com.android.systemui.statusbar.car.AssitantButton
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
- systemui:intent="intent:#Intent;action=com.google.android.demandspace.START;end"
systemui:useMoreIcon="false"
/>
+
</LinearLayout>
<LinearLayout
@@ -138,10 +137,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:paddingStart="@*android:dimen/car_keyline_1"
- android:paddingEnd="@*android:dimen/car_keyline_1"
+ android:paddingStart="@dimen/car_keyline_1"
+ android:paddingEnd="@dimen/car_keyline_1"
android:gravity="center"
android:visibility="gone">
+
</LinearLayout>
-</com.android.systemui.statusbar.car.CarNavigationBarView>
+</com.android.systemui.statusbar.car.CarNavigationBarView>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
index 72ec8d8..d36d1d6 100644
--- a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
@@ -25,6 +25,9 @@
android:orientation="vertical"
android:background="@drawable/system_bar_background">
+ <!-- phone.NavigationBarView has rot0 and rot90 but we expect the car head unit to have a fixed
+ rotation so skip this level of the hierarchy.
+ -->
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
@@ -48,6 +51,18 @@
/>
<com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/grid"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ systemui:intent="intent:#Intent;component=com.android.car.home/.AppGridActivity;launchFlags=0x14000000;end"
+ systemui:longIntent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ android:src="@drawable/car_ic_apps"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingTop="30dp"
+ android:paddingBottom="30dp"
+ />
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -58,6 +73,7 @@
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
+
</LinearLayout>
<LinearLayout
@@ -78,6 +94,7 @@
android:alpha="0.7"
/>
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
new file mode 100644
index 0000000..8247211
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/car_top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/system_bar_background"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <FrameLayout
+ android:id="@+id/left_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacleft"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/lefttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@*android:dimen/car_padding_4"
+ android:paddingEnd="16dp"
+ android:gravity="center_vertical|start"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.CarStatus"
+ systemui:hvacAreaId="49"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/clock_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerInParent="true">
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/qs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"/>
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:elevation="5dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/system_icon_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@+id/clock_container"
+ android:paddingStart="@*android:dimen/car_padding_1"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ >
+
+ <include
+ layout="@layout/system_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="4dp"
+ android:gravity="center_vertical"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/right_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacright"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/righttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="@*android:dimen/car_padding_4"
+ android:gravity="center_vertical|end"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.CarStatus"
+ systemui:hvacAreaId="68"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+ </RelativeLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 08d48bf..37cd1d4 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -69,10 +69,10 @@
android:visibility="gone"
/>
- <include layout="@layout/car_top_navigation_bar"
+ <FrameLayout
+ android:id="@+id/car_top_navigation_bar_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
+ android:layout_height="wrap_content"/>
</LinearLayout>
<include layout="@layout/brightness_mirror"/>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index fb422af..e2da91b 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -120,4 +120,73 @@
<dimen name="notification_shade_handle_bar_margin_bottom">10dp</dimen>
<dimen name="notification_shade_list_padding_bottom">50dp</dimen>
+ <!-- The alpha for the scrim behind the notification shade. This value is 1 so that the
+ scrim has no transparency. -->
+ <item name="scrim_behind_alpha" format="float" type="dimen">1.0</item>
+
+ <!-- The width of panel holding the notification card. -->
+ <dimen name="notification_panel_width">522dp</dimen>
+
+ <!-- The width of the quick settings panel. -1 for match_parent. -->
+ <dimen name="qs_panel_width">-1px</dimen>
+
+ <!-- Height of a small notification in the status bar-->
+ <dimen name="notification_min_height">192dp</dimen>
+
+ <!-- Height of a small notification in the status bar which was used before android N -->
+ <dimen name="notification_min_height_legacy">192dp</dimen>
+
+ <!-- Height of a large notification in the status bar -->
+ <dimen name="notification_max_height">400dp</dimen>
+
+ <!-- Height of a heads up notification in the status bar for legacy custom views -->
+ <dimen name="notification_max_heads_up_height_legacy">400dp</dimen>
+
+ <!-- Height of a heads up notification in the status bar -->
+ <dimen name="notification_max_heads_up_height">400dp</dimen>
+
+ <!-- Height of the status bar header bar -->
+ <dimen name="status_bar_header_height">54dp</dimen>
+
+ <!-- The height of the divider between the individual notifications. -->
+ <dimen name="notification_divider_height">16dp</dimen>
+
+ <!-- The height of the divider between the individual notifications when the notification
+ wants it to be increased. This value is the same as notification_divider_height so that
+ the spacing between all notifications will always be the same. -->
+ <dimen name="notification_divider_height_increased">@dimen/notification_divider_height</dimen>
+
+ <!-- The alpha of the dividing line between child notifications of a notification group. -->
+ <item name="notification_divider_alpha" format="float" type="dimen">1.0</item>
+
+ <!-- The width of each individual notification card. -->
+ <dimen name="notification_child_width">522dp</dimen>
+
+ <!-- The top margin of the notification panel. -->
+ <dimen name="notification_panel_margin_top">32dp</dimen>
+
+ <!-- The bottom margin of the panel that holds the list of notifications. -->
+ <dimen name="notification_panel_margin_bottom">@dimen/notification_divider_height</dimen>
+
+ <!-- The corner radius of the shadow behind the notification. -->
+ <dimen name="notification_shadow_radius">16dp</dimen>
+
+ <!-- The amount of space below the notification list. This value is 0 so the list scrolls
+ all the way to the bottom. -->
+ <dimen name="close_handle_underlap">0dp</dimen>
+
+ <!-- The height of the divider between the individual notifications in a notification group. -->
+ <dimen name="notification_children_container_divider_height">1dp</dimen>
+
+ <!-- The height of the header for a container containing child notifications. -->
+ <dimen name="notification_children_container_header_height">76dp</dimen>
+
+ <!-- The top margin for the notification children container in its non-expanded form. This
+ value is smaller than notification_children_container_header_height to bring the first
+ child closer so there is less wasted space. -->
+ <dimen name="notification_children_container_margin_top">68dp</dimen>
+
+ <!-- The height of the quick settings footer that holds the user switcher, settings icon,
+ etc. in the car setting.-->
+ <dimen name="qs_footer_height">74dp</dimen>
</resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index c7654e8..be2d542 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -23,8 +23,6 @@
import com.android.systemui.statusbar.car.CarFacetButtonController;
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.volume.CarVolumeDialogComponent;
-import com.android.systemui.volume.VolumeDialogComponent;
import javax.inject.Singleton;
@@ -57,10 +55,6 @@
return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
}
- public VolumeDialogComponent createVolumeDialogComponent(SystemUI systemUi, Context context) {
- return new CarVolumeDialogComponent(systemUi, context);
- }
-
@Singleton
@Component(modules = ContextHolder.class)
public interface CarDependencyComponent {
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 5ec1bae..b1067f8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -36,6 +36,8 @@
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.volume.CarVolumeDialogComponent;
+import com.android.systemui.volume.VolumeDialogComponent;
import javax.inject.Named;
import javax.inject.Singleton;
@@ -102,4 +104,8 @@
@IntoMap
@ClassKey(StatusBar.class)
public abstract SystemUI providesStatusBar(CarStatusBar statusBar);
+
+ @Binds
+ abstract VolumeDialogComponent bindVolumeDialogComponent(
+ CarVolumeDialogComponent carVolumeDialogComponent);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 681d8f5..90aba2f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -113,6 +113,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -162,9 +163,11 @@
private BatteryMeterView mBatteryMeterView;
private Drawable mNotificationPanelBackground;
+ private ViewGroup mTopNavigationBarContainer;
private ViewGroup mNavigationBarWindow;
private ViewGroup mLeftNavigationBarWindow;
private ViewGroup mRightNavigationBarWindow;
+ private CarNavigationBarView mTopNavigationBarView;
private CarNavigationBarView mNavigationBarView;
private CarNavigationBarView mLeftNavigationBarView;
private CarNavigationBarView mRightNavigationBarView;
@@ -176,7 +179,7 @@
private CarFacetButtonController mCarFacetButtonController;
private ActivityManagerWrapper mActivityManagerWrapper;
private DeviceProvisionedController mDeviceProvisionedController;
- private boolean mDeviceIsProvisioned = true;
+ private boolean mDeviceIsSetUpForUser = true;
private HvacController mHvacController;
private DrivingStateHelper mDrivingStateHelper;
private PowerManagerHelper mPowerManagerHelper;
@@ -197,6 +200,9 @@
private boolean mNotificationListAtBottom;
// Was the notification list at the bottom when the user first touched the screen
private boolean mNotificationListAtBottomAtTimeOfTouch;
+ // To be attached to the top navigation bar (i.e. status bar) to pull down the notification
+ // panel.
+ private View.OnTouchListener mTopNavBarNotificationTouchListener;
// To be attached to the navigation bars such that they can close the notification panel if
// it's open.
private View.OnTouchListener mNavBarNotificationTouchListener;
@@ -245,6 +251,7 @@
@Inject
public CarStatusBar(
+ Context context,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -298,8 +305,10 @@
ConfigurationController configurationController,
StatusBarWindowController statusBarWindowController,
StatusBarWindowViewController.Builder statusBarWindowViewControllerBuild,
- NotifLog notifLog) {
+ NotifLog notifLog,
+ DozeParameters dozeParameters) {
super(
+ context,
lightBarController,
autoHideController,
keyguardUpdateMonitor,
@@ -353,7 +362,8 @@
configurationController,
statusBarWindowController,
statusBarWindowViewControllerBuild,
- notifLog);
+ notifLog,
+ dozeParameters);
mNavigationBarController = navigationBarController;
}
@@ -362,7 +372,7 @@
// get the provisioned state before calling the parent class since it's that flow that
// builds the nav bar
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
- mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned();
+ mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
// Keyboard related setup, before nav bars are created.
mHideNavBarForKeyboard = mContext.getResources().getBoolean(
@@ -374,6 +384,10 @@
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
mScreenLifecycle.addObserver(mScreenObserver);
+ // Need to initialize HVAC controller before calling super.start - before system bars are
+ // created.
+ mHvacController = new HvacController(mContext);
+
super.start();
mTaskStackListener = new TaskStackListenerImpl();
mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
@@ -392,25 +406,18 @@
mHvacController.connectToCarService();
- CarSystemUIFactory factory = SystemUIFactory.getInstance();
- if (!mDeviceIsProvisioned) {
- mDeviceProvisionedController.addCallback(
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- mHandler.post(() -> {
- // on initial boot we are getting a call even though the value
- // is the same so we are confirming the reset is needed
- boolean deviceProvisioned =
- mDeviceProvisionedController.isDeviceProvisioned();
- if (mDeviceIsProvisioned != deviceProvisioned) {
- mDeviceIsProvisioned = deviceProvisioned;
- restartNavBars();
- }
- });
- }
- });
- }
+ mDeviceProvisionedController.addCallback(
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+
+ @Override
+ public void onUserSwitched() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+ });
// Register a listener for driving state changes.
mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
@@ -422,6 +429,14 @@
mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
}
+ private void restartNavBarsIfNecessary() {
+ boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+ if (mDeviceIsSetUpForUser != currentUserSetup) {
+ mDeviceIsSetUpForUser = currentUserSetup;
+ restartNavBars();
+ }
+ }
+
/**
* Remove all content from navbars and rebuild them. Used to allow for different nav bars
* before and after the device is provisioned. . Also for change of density and font size.
@@ -430,8 +445,8 @@
// remove and reattach all hvac components such that we don't keep a reference to unused
// ui elements
mHvacController.removeAllComponents();
- addTemperatureViewToController(mStatusBarWindow);
mCarFacetButtonController.removeAll();
+
if (mNavigationBarWindow != null) {
mNavigationBarWindow.removeAllViews();
mNavigationBarView = null;
@@ -525,7 +540,6 @@
@Override
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
super.makeStatusBarView(result);
- mHvacController = new HvacController(mContext);
CarSystemUIFactory factory = SystemUIFactory.getInstance();
mCarFacetButtonController = factory.getCarDependencyComponent()
@@ -550,7 +564,8 @@
* touch listeners needed for opening and closing the notification panel
*/
private void connectNotificationsUI() {
- // Attached to the status bar to detect pull down of the notification shade.
+ // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
+ // notification shade.
GestureDetector openGestureDetector = new GestureDetector(mContext,
new OpenNotificationGestureListener() {
@Override
@@ -583,6 +598,18 @@
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new HandleBarCloseNotificationGestureListener());
+ mTopNavBarNotificationTouchListener = (v, event) -> {
+ if (!mDeviceIsSetUpForUser) {
+ return true;
+ }
+ boolean consumed = openGestureDetector.onTouchEvent(event);
+ if (consumed) {
+ return true;
+ }
+ maybeCompleteAnimation(event);
+ return true;
+ };
+
mNavBarNotificationTouchListener =
(v, event) -> {
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
@@ -593,21 +620,6 @@
return true;
};
- // The following are the ui elements that the user would call the status bar.
- // This will set the status bar so it they can make call backs.
- CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar);
- topBar.setStatusBar(this);
- topBar.setStatusBarWindowTouchListener((v1, event1) -> {
-
- boolean consumed = openGestureDetector.onTouchEvent(event1);
- if (consumed) {
- return true;
- }
- maybeCompleteAnimation(event1);
- return true;
- }
- );
-
mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(mBarService);
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
if (launchResult == ActivityManager.START_TASK_TO_FRONT
@@ -914,23 +926,30 @@
}
private void buildNavBarContent() {
+ // Always build top bar.
+ buildTopBar((mDeviceIsSetUpForUser) ? R.layout.car_top_navigation_bar :
+ R.layout.car_top_navigation_bar_unprovisioned);
+
if (mShowBottom) {
- buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar :
+ buildBottomBar((mDeviceIsSetUpForUser) ? R.layout.car_navigation_bar :
R.layout.car_navigation_bar_unprovisioned);
}
if (mShowLeft) {
- buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar :
+ buildLeft((mDeviceIsSetUpForUser) ? R.layout.car_left_navigation_bar :
R.layout.car_left_navigation_bar_unprovisioned);
}
if (mShowRight) {
- buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar :
+ buildRight((mDeviceIsSetUpForUser) ? R.layout.car_right_navigation_bar :
R.layout.car_right_navigation_bar_unprovisioned);
}
}
private void buildNavBarWindows() {
+ mTopNavigationBarContainer = mStatusBarWindow
+ .findViewById(R.id.car_top_navigation_bar_container);
+
if (mShowBottom) {
mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
R.layout.navigation_bar_window, null);
@@ -963,15 +982,25 @@
}
boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
- if (!isKeyboardVisible) {
- attachBottomNavBarWindow();
- } else {
- detachBottomNavBarWindow();
- }
+ showBottomNavBarWindow(isKeyboardVisible);
}
private void attachNavBarWindows() {
- attachBottomNavBarWindow();
+ if (mShowBottom && !mBottomNavBarVisible) {
+ mBottomNavBarVisible = true;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ lp.setTitle("CarNavigationBar");
+ lp.windowAnimations = 0;
+ mWindowManager.addView(mNavigationBarWindow, lp);
+ }
if (mShowLeft) {
int width = mContext.getResources().getDimensionPixelSize(
@@ -1009,47 +1038,32 @@
}
}
- /**
- * Attaches the bottom nav bar window. Can be extended to modify the specific behavior of
- * attaching the bottom nav bar.
- */
- protected void attachBottomNavBarWindow() {
+ private void showBottomNavBarWindow(boolean isKeyboardVisible) {
if (!mShowBottom) {
return;
}
- if (mBottomNavBarVisible) {
+ // If keyboard is visible and bottom nav bar not visible, this is the correct state, so do
+ // nothing. Same with if keyboard is not visible and bottom nav bar is visible.
+ if (isKeyboardVisible ^ mBottomNavBarVisible) {
return;
}
- mBottomNavBarVisible = true;
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSLUCENT);
- lp.setTitle("CarNavigationBar");
- lp.windowAnimations = 0;
- mWindowManager.addView(mNavigationBarWindow, lp);
+ mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE);
+ mBottomNavBarVisible = !isKeyboardVisible;
}
- /**
- * Detaches the bottom nav bar window. Can be extended to modify the specific behavior of
- * detaching the bottom nav bar.
- */
- protected void detachBottomNavBarWindow() {
- if (!mShowBottom) {
- return;
+ private void buildTopBar(int layout) {
+ mTopNavigationBarContainer.removeAllViews();
+ View.inflate(mContext, layout, mTopNavigationBarContainer);
+ mTopNavigationBarView = (CarNavigationBarView) mTopNavigationBarContainer.getChildAt(0);
+ if (mTopNavigationBarView == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_top_navigation_bar");
+ throw new RuntimeException("Unable to build top nav bar due to missing layout");
}
-
- if (!mBottomNavBarVisible) {
- return;
- }
- mBottomNavBarVisible = false;
- mWindowManager.removeView(mNavigationBarWindow);
+ mTopNavigationBarView.setStatusBar(this);
+ addTemperatureViewToController(mTopNavigationBarView);
+ mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
}
private void buildBottomBar(int layout) {
@@ -1060,7 +1074,7 @@
mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
if (mNavigationBarView == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
- throw new RuntimeException("Unable to build botom nav bar due to missing layout");
+ throw new RuntimeException("Unable to build bottom nav bar due to missing layout");
}
mNavigationBarView.setStatusBar(this);
addTemperatureViewToController(mNavigationBarView);
@@ -1071,7 +1085,7 @@
View.inflate(mContext, layout, mLeftNavigationBarWindow);
mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
if (mLeftNavigationBarView == null) {
- Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_left_navigation_bar");
throw new RuntimeException("Unable to build left nav bar due to missing layout");
}
mLeftNavigationBarView.setStatusBar(this);
@@ -1084,7 +1098,7 @@
View.inflate(mContext, layout, mRightNavigationBarWindow);
mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
if (mRightNavigationBarView == null) {
- Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_right_navigation_bar");
throw new RuntimeException("Unable to build right nav bar due to missing layout");
}
mRightNavigationBarView.setStatusBar(this);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 886162f..f7e5d01 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -34,6 +34,7 @@
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -52,6 +53,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Displays a GridLayout with icons for the users in the system to allow switching between users.
@@ -61,6 +64,7 @@
private UserSelectionListener mUserSelectionListener;
private UserAdapter mAdapter;
private CarUserManagerHelper mCarUserManagerHelper;
+ private UserManager mUserManager;
private Context mContext;
private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() {
@@ -74,6 +78,7 @@
super(context, attrs);
mContext = context;
mCarUserManagerHelper = new CarUserManagerHelper(mContext);
+ mUserManager = UserManager.get(mContext);
addItemDecoration(new ItemSpacingDecoration(context.getResources().getDimensionPixelSize(
R.dimen.car_user_switcher_vertical_spacing_between_users)));
@@ -103,12 +108,23 @@
* @return the adapter
*/
public void buildAdapter() {
- List<UserRecord> userRecords = createUserRecords(mCarUserManagerHelper
- .getAllUsers());
+ List<UserRecord> userRecords = createUserRecords(getAllUsers());
mAdapter = new UserAdapter(mContext, userRecords);
super.setAdapter(mAdapter);
}
+ private List<UserInfo> getAllUsers() {
+ Stream<UserInfo> userListStream =
+ mUserManager.getUsers(/* excludeDying= */ true).stream();
+
+ if (UserManager.isHeadlessSystemUserMode()) {
+ userListStream =
+ userListStream.filter(userInfo -> userInfo.id != UserHandle.USER_SYSTEM);
+ }
+ userListStream = userListStream.filter(userInfo -> userInfo.supportsSwitchToByUser());
+ return userListStream.collect(Collectors.toList());
+ }
+
private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
List<UserRecord> userRecords = new ArrayList<>();
@@ -173,7 +189,7 @@
private void onUsersUpdate() {
mAdapter.clearUsers();
- mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper.getAllUsers()));
+ mAdapter.updateUsers(createUserRecords(getAllUsers()));
mAdapter.notifyDataSetChanged();
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
index ead1de2..88d641e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
@@ -47,6 +47,7 @@
public class OngoingPrivacyChip extends LinearLayout implements View.OnClickListener {
private Context mContext;
+ private Handler mHandler;
private LinearLayout mIconsContainer;
private List<PrivacyItem> mPrivacyItems;
@@ -88,6 +89,7 @@
private void init(Context context) {
mContext = context;
+ mHandler = new Handler(Looper.getMainLooper());
mPrivacyItems = new ArrayList<>();
sAppOpsController = Dependency.get(AppOpsController.class);
mUserManager = mContext.getSystemService(UserManager.class);
@@ -131,8 +133,7 @@
@Override
public void onClick(View v) {
updatePrivacyList();
- Handler mUiHandler = new Handler(Looper.getMainLooper());
- mUiHandler.post(() -> {
+ mHandler.post(() -> {
mActivityStarter.postStartActivityDismissingKeyguard(
new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
});
@@ -152,21 +153,17 @@
}
private void updatePrivacyList() {
- mPrivacyItems = mCurrentUserIds.stream()
+ List<PrivacyItem> privacyItems = mCurrentUserIds.stream()
.flatMap(item -> sAppOpsController.getActiveAppOpsForUser(item).stream())
.filter(Objects::nonNull)
.map(item -> toPrivacyItem(item))
.filter(Objects::nonNull)
.collect(Collectors.toList());
- mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems);
-
- Handler refresh = new Handler(Looper.getMainLooper());
- refresh.post(new Runnable() {
- @Override
- public void run() {
- updateView();
- }
- });
+ if (!privacyItems.equals(mPrivacyItems)) {
+ mPrivacyItems = privacyItems;
+ mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems);
+ mHandler.post(this::updateView);
+ }
}
private PrivacyItem toPrivacyItem(AppOpItem appOpItem) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
index 5ec7a77..820bea9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
@@ -23,16 +23,20 @@
import android.graphics.drawable.Drawable;
import android.util.Log;
+import java.util.Objects;
+
/**
* Class to hold the data for the applications that are using the AppOps permissions.
*/
public class PrivacyApplication {
private static final String TAG = "PrivacyApplication";
+ private String mPackageName;
private Drawable mIcon;
private String mApplicationName;
public PrivacyApplication(String packageName, Context context) {
+ mPackageName = packageName;
try {
CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context);
ApplicationInfo app = context.getPackageManager()
@@ -59,4 +63,17 @@
public String getApplicationName() {
return mApplicationName;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrivacyApplication that = (PrivacyApplication) o;
+ return mPackageName.equals(that.mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName);
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
index fca1373..d3e123e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.car.privacy;
+import java.util.Objects;
+
/**
* Class for holding the data of each privacy item displayed in {@link OngoingPrivacyDialog}
*/
@@ -43,4 +45,18 @@
public PrivacyType getPrivacyType() {
return mPrivacyType;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrivacyItem that = (PrivacyItem) o;
+ return mPrivacyType == that.mPrivacyType
+ && mPrivacyApplication.equals(that.mPrivacyApplication);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPrivacyType, mPrivacyApplication);
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
index 71cc19b..4d6af95 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
@@ -19,15 +19,21 @@
import android.content.Context;
import com.android.systemui.SystemUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.VolumeDialog;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Allows for adding car specific dialog when the volume dialog is created.
*/
+@Singleton
public class CarVolumeDialogComponent extends VolumeDialogComponent {
- public CarVolumeDialogComponent(SystemUI sysui, Context context) {
- super(sysui, context);
+ @Inject
+ public CarVolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator) {
+ super(context, keyguardViewMediator);
}
protected VolumeDialog createDefault() {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 81c5bcd..642dc82 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -35,7 +35,6 @@
import android.net.http.SslError;
import android.os.Bundle;
import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -477,11 +476,11 @@
}
private static void logd(String s) {
- Rlog.d(TAG, s);
+ Log.d(TAG, s);
}
private static void loge(String s) {
- Rlog.d(TAG, s);
+ Log.d(TAG, s);
}
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index 02c61d7..46b1d5f 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -19,7 +19,6 @@
import android.content.Intent;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
import android.text.TextUtils;
import android.util.Log;
@@ -27,7 +26,6 @@
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
/**
@@ -44,7 +42,7 @@
private static final String INTER_GROUP_DELIMITER = "\\s*:\\s*";
private static final String TAG = CustomConfigLoader.class.getSimpleName();
- private static final boolean VDBG = Rlog.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
/**
* loads and parses the carrier config, return a list of carrier action for the given signal
@@ -70,7 +68,7 @@
// return an empty list if no match found
List<Integer> actionList = new ArrayList<>();
if (carrierConfigManager == null) {
- Rlog.e(TAG, "load carrier config failure with carrier config manager uninitialized");
+ Log.e(TAG, "load carrier config failure with carrier config manager uninitialized");
return actionList;
}
PersistableBundle b = carrierConfigManager.getConfig();
@@ -101,8 +99,8 @@
.EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
break;
default:
- Rlog.e(TAG, "load carrier config failure with un-configured key: " +
- intent.getAction());
+ Log.e(TAG, "load carrier config failure with un-configured key: "
+ + intent.getAction());
break;
}
if (!ArrayUtils.isEmpty(configs)) {
@@ -111,12 +109,12 @@
matchConfig(config, arg1, arg2, actionList);
if (!actionList.isEmpty()) {
// return the first match
- if (VDBG) Rlog.d(TAG, "found match action list: " + actionList.toString());
+ if (VDBG) Log.d(TAG, "found match action list: " + actionList.toString());
return actionList;
}
}
}
- Rlog.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1
+ Log.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1
+ "arg2: " + arg2);
}
return actionList;
@@ -166,7 +164,7 @@
try {
actionList.add(Integer.parseInt(idx));
} catch (NumberFormatException e) {
- Rlog.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): "
+ Log.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): "
+ e);
}
}
diff --git a/packages/EncryptedLocalTransport/Android.bp b/packages/EncryptedLocalTransport/Android.bp
new file mode 100644
index 0000000..dd30ad1
--- /dev/null
+++ b/packages/EncryptedLocalTransport/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_app {
+ name: "EncryptedLocalTransport",
+ srcs: ["src/**/*.java"],
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
+ static_libs: ["LocalTransport"],
+ platform_apis: true,
+ certificate: "platform",
+ privileged: true,
+}
diff --git a/packages/EncryptedLocalTransport/AndroidManifest.xml b/packages/EncryptedLocalTransport/AndroidManifest.xml
new file mode 100644
index 0000000..dc3617f
--- /dev/null
+++ b/packages/EncryptedLocalTransport/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2019 Google Inc.
+ *
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.encryptedlocaltransport"
+ android:sharedUserId="android.uid.system" >
+
+
+ <application android:allowBackup="false" >
+ <!-- This service does not need to be exported because it shares uid with the system server
+ which is the only client. -->
+ <service android:name=".EncryptedLocalTransportService"
+ android:permission="android.permission.CONFIRM_FULL_BACKUP"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.backup.TRANSPORT_HOST" />
+ </intent-filter>
+ </service>
+
+ </application>
+</manifest>
diff --git a/packages/EncryptedLocalTransport/proguard.flags b/packages/EncryptedLocalTransport/proguard.flags
new file mode 100644
index 0000000..e4ce3c5
--- /dev/null
+++ b/packages/EncryptedLocalTransport/proguard.flags
@@ -0,0 +1,2 @@
+-keep class com.android.localTransport.EncryptedLocalTransport
+-keep class com.android.localTransport.EncryptedLocalTransportService
diff --git a/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java
new file mode 100644
index 0000000..3dd453e
--- /dev/null
+++ b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.encryptedlocaltransport;
+
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Log;
+
+import com.android.localtransport.LocalTransport;
+import com.android.localtransport.LocalTransportParameters;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+
+public class EncryptedLocalTransport extends LocalTransport {
+ private static final String TAG = "EncryptedLocalTransport";
+ private static final int BACKUP_BUFFER_SIZE = 32 * 1024; // 32 KB.
+
+ public EncryptedLocalTransport(Context context,
+ LocalTransportParameters parameters) {
+ super(context, parameters);
+ }
+
+ @Override
+ public int performBackup(
+ PackageInfo packageInfo, ParcelFileDescriptor data, int flags) {
+ File packageFile;
+ try {
+ StructStat stat = Os.fstat(data.getFileDescriptor());
+ if (stat.st_size > KEY_VALUE_BACKUP_SIZE_QUOTA) {
+ Log.w(TAG, "New datastore size " + stat.st_size
+ + " exceeds quota " + KEY_VALUE_BACKUP_SIZE_QUOTA);
+ return TRANSPORT_QUOTA_EXCEEDED;
+ }
+ } catch (ErrnoException e) {
+ Log.w(TAG, "Failed to stat the backup input file: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ clearBackupData(packageInfo);
+
+ try (InputStream in = new FileInputStream(data.getFileDescriptor())) {
+ packageFile = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
+ Files.copy(in, packageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to save backup data to file: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ return TRANSPORT_OK;
+ }
+
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) {
+ if (mRestorePackages == null) {
+ throw new IllegalStateException("startRestore not called");
+ }
+ if (mRestorePackage < 0) {
+ throw new IllegalStateException("nextRestorePackage not called");
+ }
+ if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
+ throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
+ }
+
+ try(OutputStream out = new FileOutputStream(outFd.getFileDescriptor())) {
+ File packageFile = new File(mRestoreSetIncrementalDir,
+ mRestorePackages[mRestorePackage].packageName);
+ Files.copy(packageFile.toPath(), out);
+ } catch (IOException e) {
+ Log.d(TAG, "Failed to transfer restore data: " + e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ return BackupTransport.TRANSPORT_OK;
+ }
+
+ @Override
+ protected boolean hasRestoreDataForPackage(String packageName) {
+ File contents = (new File(mRestoreSetIncrementalDir, packageName));
+ return contents.exists() && contents.length() != 0;
+
+ }
+}
diff --git a/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java
new file mode 100644
index 0000000..952f90d
--- /dev/null
+++ b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.encryptedlocaltransport;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.localtransport.LocalTransportParameters;
+
+public class EncryptedLocalTransportService extends Service {
+ private static EncryptedLocalTransport sTransport = null;
+
+ @Override
+ public void onCreate() {
+ if (sTransport == null) {
+ LocalTransportParameters parameters =
+ new LocalTransportParameters(getMainThreadHandler(), getContentResolver());
+ sTransport = new EncryptedLocalTransport(this, parameters);
+ }
+ sTransport.getParameters().start();
+ }
+
+ @Override
+ public void onDestroy() {
+ sTransport.getParameters().stop();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return sTransport.getBinder();
+ }
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 1b27b52..48d34ae 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,7 @@
package com.android.externalstorage;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.StorageStatsManager;
import android.content.ContentResolver;
@@ -298,6 +299,53 @@
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
+ /**
+ * Check that the directory is the root of storage or blocked file from tree.
+ *
+ * @param docId the docId of the directory to be checked
+ * @return true, should be blocked from tree. Otherwise, false.
+ */
+ @Override
+ protected boolean shouldBlockFromTree(@NonNull String docId) {
+ try {
+ final File dir = getFileForDocId(docId, true /* visible */).getCanonicalFile();
+ if (!dir.isDirectory()) {
+ return false;
+ }
+
+ final String path = dir.getAbsolutePath();
+
+ // Block Download folder from tree
+ if (MediaStore.Downloads.isDownloadDir(path)) {
+ return true;
+ }
+
+ final ArrayMap<String, RootInfo> roots = new ArrayMap<>();
+
+ synchronized (mRootsLock) {
+ roots.putAll(mRoots);
+ }
+
+ // block root of storage
+ for (int i = 0; i < roots.size(); i++) {
+ RootInfo rootInfo = roots.valueAt(i);
+ // skip home root
+ if (TextUtils.equals(rootInfo.rootId, ROOT_ID_HOME)) {
+ continue;
+ }
+
+ // block the root of storage
+ if (TextUtils.equals(path, rootInfo.visiblePath.getAbsolutePath())) {
+ return true;
+ }
+ }
+ return false;
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to determine if " + docId + " should block from tree " + ": " + e);
+ }
+ }
+
@Override
protected String getDocIdForFile(File file) throws FileNotFoundException {
return getDocIdForFileMaybeCreate(file, false);
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 4408ef5..50f858e 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -16,6 +16,7 @@
package com.android.localtransport;
+import android.annotation.Nullable;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@@ -71,19 +72,19 @@
// Size quotas at reasonable values, similar to the current cloud-storage limits
private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
- private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
+ protected static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
private Context mContext;
private File mDataDir;
private File mCurrentSetDir;
- private File mCurrentSetIncrementalDir;
+ protected File mCurrentSetIncrementalDir;
private File mCurrentSetFullDir;
- private PackageInfo[] mRestorePackages = null;
- private int mRestorePackage = -1; // Index into mRestorePackages
- private int mRestoreType;
+ protected PackageInfo[] mRestorePackages = null;
+ protected int mRestorePackage = -1; // Index into mRestorePackages
+ protected int mRestoreType;
private File mRestoreSetDir;
- private File mRestoreSetIncrementalDir;
+ protected File mRestoreSetIncrementalDir;
private File mRestoreSetFullDir;
// Additional bookkeeping for full backup
@@ -115,7 +116,7 @@
makeDataDirs();
}
- LocalTransportParameters getParameters() {
+ public LocalTransportParameters getParameters() {
return mParameters;
}
@@ -142,11 +143,18 @@
return null;
}
+ /** @removed Replaced with dataManagementIntentLabel in the API */
public String dataManagementLabel() {
return TRANSPORT_DATA_MANAGEMENT_LABEL;
}
@Override
+ @Nullable
+ public CharSequence dataManagementIntentLabel() {
+ return TRANSPORT_DATA_MANAGEMENT_LABEL;
+ }
+
+ @Override
public String transportDirName() {
return TRANSPORT_DIR_NAME;
}
@@ -537,14 +545,14 @@
int bytesLeft = numBytes;
while (bytesLeft > 0) {
try {
- int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
- if (nRead < 0) {
- // Something went wrong if we expect data but saw EOD
- Log.w(TAG, "Unexpected EOD; failing backup");
- return TRANSPORT_ERROR;
- }
- mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
- bytesLeft -= nRead;
+ int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
+ if (nRead < 0) {
+ // Something went wrong if we expect data but saw EOD
+ Log.w(TAG, "Unexpected EOD; failing backup");
+ return TRANSPORT_ERROR;
+ }
+ mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
+ bytesLeft -= nRead;
} catch (IOException e) {
Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
return TRANSPORT_ERROR;
@@ -620,20 +628,15 @@
}
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
- boolean found = false;
+ boolean found;
while (++mRestorePackage < mRestorePackages.length) {
String name = mRestorePackages[mRestorePackage].packageName;
// If we have key/value data for this package, deliver that
// skip packages where we have a data dir but no actual contents
- String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
- if (contents != null && contents.length > 0) {
- if (DEBUG) {
- Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
- + mRestorePackage + " = " + name);
- }
+ found = hasRestoreDataForPackage(name);
+ if (found) {
mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
- found = true;
}
if (!found) {
@@ -664,6 +667,18 @@
return RestoreDescription.NO_MORE_PACKAGES;
}
+ protected boolean hasRestoreDataForPackage(String packageName) {
+ String[] contents = (new File(mRestoreSetIncrementalDir, packageName)).list();
+ if (contents != null && contents.length > 0) {
+ if (DEBUG) {
+ Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
+ + mRestorePackage + " = " + packageName);
+ }
+ return true;
+ }
+ return false;
+ }
+
@Override
public int getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index 784be22..8b4db92 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -22,7 +22,7 @@
import android.provider.Settings;
import android.util.KeyValueListParser;
-class LocalTransportParameters extends KeyValueSettingObserver {
+public class LocalTransportParameters extends KeyValueSettingObserver {
private static final String TAG = "LocalTransportParams";
private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
@@ -31,7 +31,7 @@
private boolean mFakeEncryptionFlag;
private boolean mIsNonIncrementalOnly;
- LocalTransportParameters(Handler handler, ContentResolver resolver) {
+ public LocalTransportParameters(Handler handler, ContentResolver resolver) {
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
}
diff --git a/packages/PackageInstaller/res/values-television/themes.xml b/packages/PackageInstaller/res/values-television/themes.xml
new file mode 100644
index 0000000..5ae4957
--- /dev/null
+++ b/packages/PackageInstaller/res/values-television/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+
+ <style name="Theme.AlertDialogActivity.NoAnimation">
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
+
+ <style name="Theme.AlertDialogActivity"
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+
+ <style name="Theme.AlertDialogActivity.NoActionBar"
+ parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+ </style>
+
+</resources>
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPrintTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS
new file mode 100644
index 0000000..541dd878
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/OWNERS
@@ -0,0 +1,5 @@
+# Please reach out to Android B&R when making Settings backup changes
+alsutton@google.com
+nathch@google.com
+rthakohov@google.com
+
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
new file mode 100644
index 0000000..e425790
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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.provider.settings.backup;
+
+import android.provider.Settings;
+
+/** Device specific settings list */
+public class DeviceSpecificSettings {
+ /**
+ * The settings values which should only be restored if the target device is the
+ * same as the source device
+ *
+ * NOTE: Settings are backed up and restored in the order they appear
+ * in this array. If you have one setting depending on another,
+ * make sure that they are ordered appropriately.
+ *
+ * @hide
+ */
+ public static final String[] DEVICE_SPECIFIC_SETTINGS_TO_BACKUP = {
+ Settings.Secure.DISPLAY_DENSITY_FORCED,
+ };
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 976f336..ef67bbd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -41,8 +41,8 @@
public class SecureSettingsValidators {
/**
* All settings in {@link Secure.SETTINGS_TO_BACKUP} and {@link
- * Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP} array *must* have a non-null validator, otherwise
- * they won't be restored.
+ * DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP} array *must* have a non-null
+ * validator, otherwise they won't be restored.
*/
public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 5e2b7c8..17c621e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -51,6 +51,7 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -1992,8 +1993,11 @@
try {
LockPatternUtils lpu = new LockPatternUtils(mContext);
List<LockPatternView.Cell> cellPattern =
- LockPatternUtils.stringToPattern(lockPattern);
- lpu.saveLockPattern(cellPattern, null, UserHandle.USER_SYSTEM);
+ LockPatternUtils.byteArrayToPattern(lockPattern.getBytes());
+ lpu.setLockCredential(
+ LockscreenCredential.createPattern(cellPattern),
+ LockscreenCredential.createNone(),
+ UserHandle.USER_SYSTEM);
} catch (IllegalArgumentException e) {
// Don't want corrupted lock pattern to hang the reboot process
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f545fa6..7e60452 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -34,6 +34,7 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.settings.backup.DeviceSpecificSettings;
import android.provider.settings.backup.GlobalSettings;
import android.provider.settings.backup.SecureSettings;
import android.provider.settings.backup.SystemSettings;
@@ -641,7 +642,7 @@
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
Settings.Secure.LEGACY_RESTORE_SETTINGS,
- Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+ DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
validators = SecureSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.System.CONTENT_URI)) {
whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
@@ -1000,7 +1001,7 @@
getContentResolver()
.query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null)) {
return extractRelevantValues(
- cursor, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+ cursor, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS
new file mode 100644
index 0000000..f3241ea
--- /dev/null
+++ b/packages/SettingsProvider/test/src/android/provider/OWNERS
@@ -0,0 +1,4 @@
+per-file * = *
+
+# Please reach out to the Android B&R team for settings backup changes
+per-file SettingsBackupTest.java = alsutton@google.com, nathch@google.com, rthakohov@google.com
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8437eae..62827bc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -16,6 +16,8 @@
package android.provider;
+import static android.provider.settings.backup.DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP;
+
import static com.google.android.collect.Sets.newHashSet;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -750,7 +752,7 @@
public void secureSettingsBackedUpOrBlacklisted() {
HashSet<String> keys = new HashSet<String>();
Collections.addAll(keys, SecureSettings.SETTINGS_TO_BACKUP);
- Collections.addAll(keys, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+ Collections.addAll(keys, DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
checkSettingsBackedUpOrBlacklisted(
getCandidateSettings(Settings.Secure.class),
keys,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index a097249..665bde3 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -476,10 +476,10 @@
}
private static void addScreenshotToIntent(Intent intent, BugreportInfo info) {
- final String screenshotFileName = info.name + ".png";
- final File screenshotFile = new File(BUGREPORT_DIR, screenshotFileName);
- final String screenshotFilePath = screenshotFile.getAbsolutePath();
- if (screenshotFile.length() > 0) {
+ final File screenshotFile = info.screenshotFiles.isEmpty()
+ ? null : info.screenshotFiles.get(0);
+ if (screenshotFile != null && screenshotFile.length() > 0) {
+ final String screenshotFilePath = screenshotFile.getAbsolutePath();
intent.putExtra(EXTRA_SCREENSHOT, screenshotFilePath);
}
return;
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 83acfa0..656827a 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -46,3 +46,6 @@
#Android Auto
stenning@google.com
+#Android TV
+rgl@google.com
+
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
index cac673f..c1d4b03 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
@@ -33,4 +33,9 @@
* will add home controls to this space.
*/
void sendParentGroup(ViewGroup group);
+
+ /**
+ * When visible, will poll for updates.
+ */
+ void setVisible(boolean visible);
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index db026ca..6518924 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -186,6 +186,8 @@
return toStringBuilder().toString();
}
+ // Used in dumps to determine current state of a tile.
+ // This string may be used for CTS testing of tiles, so removing elements is discouraged.
protected StringBuilder toStringBuilder() {
final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
sb.append(",icon=").append(icon);
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 22b0ab7..3e74970 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -35,3 +35,7 @@
*;
}
-keep class androidx.core.app.CoreComponentFactory
+
+-keep public class * extends com.android.systemui.SystemUI {
+ public <init>(android.content.Context);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml
new file mode 100644
index 0000000..87684a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,21c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-1L9,20v1zM12,2C8.14,2 5,5.14 5,9c0,2.38 1.19,4.47 3,5.74L8,17c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1v-2.26c1.81,-1.27 3,-3.36 3,-5.74 0,-3.86 -3.14,-7 -7,-7zM14.85,13.1l-0.85,0.6L14,16h-4v-2.3l-0.85,-0.6C7.8,12.16 7,10.63 7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.63 -0.8,3.16 -2.15,4.1z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/home_controls.xml b/packages/SystemUI/res/layout/home_controls.xml
new file mode 100644
index 0000000..bb971c2
--- /dev/null
+++ b/packages/SystemUI/res/layout/home_controls.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/home_controls_layout"
+ android:layout_width="match_parent"
+ android:layout_height="125dp"
+ android:layout_gravity="@integer/notification_panel_layout_gravity"
+ android:visibility="gone"
+ android:padding="8dp"
+ android:layout_margin="5dp"
+ android:background="?android:attr/colorBackgroundFloating"
+ android:orientation="vertical">
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 0e59a41..4869be1 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -51,17 +51,7 @@
systemui:viewType="com.android.systemui.plugins.qs.QS" />
<!-- Temporary area to test out home controls -->
- <LinearLayout
- android:id="@+id/home_controls_layout"
- android:layout_width="match_parent"
- android:layout_height="125dp"
- android:layout_gravity="@integer/notification_panel_layout_gravity"
- android:visibility="gone"
- android:padding="8dp"
- android:layout_margin="5dp"
- android:background="?android:attr/colorBackgroundFloating"
- android:orientation="vertical">
- </LinearLayout>
+ <include layout="@layout/home_controls" />
<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
@@ -107,4 +97,4 @@
android:background="@drawable/qs_navbar_scrim" />
<include layout="@layout/status_bar_expanded_plugin_frame"/>
-</merge>
\ No newline at end of file
+</merge>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 105b27e..efcc2c4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -117,7 +117,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8335c11..3cc683a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -129,6 +129,9 @@
<!-- Prompt for the USB device confirm dialog [CHAR LIMIT=80] -->
<string name="usb_device_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_device">%2$s</xliff:g>?</string>
+ <!-- Prompt for the USB device confirm dialog with warning text for USB device dialogs. [CHAR LIMIT=200] -->
+ <string name="usb_device_confirm_prompt_warn">Open <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> to handle <xliff:g id="usb_device" example="USB Headphones">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device.</string>
+
<!-- Prompt for the USB accessory confirm dialog [CHAR LIMIT=80] -->
<string name="usb_accessory_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_accessory">%2$s</xliff:g>?</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 2ef0422..748f356 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -115,4 +115,15 @@
Log.e(TAG, "Failed to clean up screenshot of recents animation", e);
}
}
+
+ /**
+ * @see {{@link IRecentsAnimationController#setWillFinishToHome(boolean)}}.
+ */
+ public void setWillFinishToHome(boolean willFinishToHome) {
+ try {
+ mAnimationController.setWillFinishToHome(willFinishToHome);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set overview reached state", e);
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index ad182fe..22d1675c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -53,7 +53,6 @@
public static final int TRANSIT_WALLPAPER_INTRA_CLOSE =
WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE;
public static final int TRANSIT_TASK_OPEN_BEHIND = WindowManager.TRANSIT_TASK_OPEN_BEHIND;
- public static final int TRANSIT_TASK_IN_PLACE = WindowManager.TRANSIT_TASK_IN_PLACE;
public static final int TRANSIT_ACTIVITY_RELAUNCH = WindowManager.TRANSIT_ACTIVITY_RELAUNCH;
public static final int TRANSIT_DOCK_TASK_FROM_RECENTS =
WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index ef9538d..46b4c6b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -170,7 +170,7 @@
mSeparator = separator;
mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE)).getMaxPhoneCount();
+ Context.TELEPHONY_SERVICE)).getSupportedModemCount();
mSimErrorState = new boolean[mSimSlotsNumber];
mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index d45603f..ebac3d9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,10 +33,9 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
-import java.util.Arrays;
-
/**
* Base class for PIN and password unlock screens.
*/
@@ -132,19 +131,20 @@
protected void verifyPasswordAndUnlock() {
if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
- final byte[] entry = getPasswordText();
+ final LockscreenCredential password =
+ LockscreenCredential.createPassword(getPasswordText());
setPasswordEntryInputEnabled(false);
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
}
final int userId = KeyguardUpdateMonitor.getCurrentUser();
- if (entry.length <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
+ if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
// to avoid accidental lockout, only count attempts that are long enough to be a
// real password. This may require some tweaking.
setPasswordEntryInputEnabled(true);
onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
return;
}
@@ -152,9 +152,9 @@
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
- mPendingLockCheck = LockPatternChecker.checkPassword(
+ mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
- entry,
+ password,
userId,
new LockPatternChecker.OnCheckCallback() {
@@ -166,7 +166,7 @@
}
onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPassword */);
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
}
@Override
@@ -181,7 +181,7 @@
onPasswordChecked(userId, false /* matched */, timeoutMs,
true /* isValidPassword */);
}
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
}
@Override
@@ -192,7 +192,7 @@
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
}
});
}
@@ -223,7 +223,7 @@
}
protected abstract void resetPasswordText(boolean animate, boolean announce);
- protected abstract byte[] getPasswordText();
+ protected abstract CharSequence getPasswordText();
protected abstract void setPasswordEntryEnabled(boolean enabled);
protected abstract void setPasswordEntryInputEnabled(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index e3ac0f6..12c9fc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -243,8 +243,8 @@
}
@Override
- protected byte[] getPasswordText() {
- return charSequenceToByteArray(mPasswordEntry.getText());
+ protected CharSequence getPasswordText() {
+ return mPasswordEntry.getText();
}
@Override
@@ -379,18 +379,4 @@
return getContext().getString(
com.android.internal.R.string.keyguard_accessibility_password_unlock);
}
-
- /*
- * This method avoids creating a new string when getting a byte array from EditView#getText().
- */
- private static byte[] charSequenceToByteArray(CharSequence chars) {
- if (chars == null) {
- return null;
- }
- byte[] bytes = new byte[chars.length()];
- for (int i = 0; i < chars.length(); i++) {
- bytes[i] = (byte) chars.charAt(i);
- }
- return bytes;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 297052f..9eb168a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -39,6 +39,7 @@
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
@@ -297,9 +298,9 @@
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
- mPendingLockCheck = LockPatternChecker.checkPattern(
+ mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
- pattern,
+ LockscreenCredential.createPattern(pattern),
userId,
new LockPatternChecker.OnCheckCallback() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 274f739..8e9df55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -167,8 +167,8 @@
}
@Override
- protected byte[] getPasswordText() {
- return charSequenceToByteArray(mPasswordEntry.getText());
+ protected CharSequence getPasswordText() {
+ return mPasswordEntry.getText();
}
@Override
@@ -266,18 +266,4 @@
return getContext().getString(
com.android.internal.R.string.keyguard_accessibility_pin_unlock);
}
-
- /*
- * This method avoids creating a new string when getting a byte array from EditView#getText().
- */
- private static byte[] charSequenceToByteArray(CharSequence chars) {
- if (chars == null) {
- return null;
- }
- byte[] bytes = new byte[chars.length()];
- for (int i = 0; i < chars.length(); i++) {
- bytes[i] = (byte) chars.charAt(i);
- }
- return bytes;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index af4e61b..5d35169 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.graphics.text.LineBreaker;
import android.net.Uri;
import android.os.Trace;
import android.provider.Settings;
@@ -152,6 +153,7 @@
mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
R.dimen.header_row_font_size);
mTitle.setOnClickListener(this);
+ mTitle.setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9bcccbc..ac5e255 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -99,6 +99,7 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DejankUtils;
import com.android.systemui.R;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -1437,6 +1438,8 @@
}
private void handleScreenTurnedOff() {
+ final String tag = "KeyguardUpdateMonitor#handleScreenTurnedOff";
+ DejankUtils.startDetectingBlockingIpcs(tag);
checkIsHandlerThread();
mHardwareFingerprintUnavailableRetryCount = 0;
mHardwareFaceUnavailableRetryCount = 0;
@@ -1446,6 +1449,7 @@
cb.onScreenTurnedOff();
}
}
+ DejankUtils.stopDetectingBlockingIpcs(tag);
}
private void handleDreamingStateChanged(int dreamStart) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 07bfa71..ff8a932 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -17,6 +17,7 @@
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.INotificationManager;
+import android.app.IWallpaperManager;
import android.content.res.Configuration;
import android.hardware.SensorPrivacyManager;
import android.hardware.display.NightDisplayListener;
@@ -77,6 +78,7 @@
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
@@ -318,6 +320,8 @@
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
@Inject Lazy<AlarmManager> mAlarmManager;
@Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel;
+ @Inject Lazy<DozeParameters> mDozeParameters;
+ @Inject Lazy<IWallpaperManager> mWallpaperManager;
@Inject
public Dependency() {
@@ -504,6 +508,8 @@
mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
mProviders.put(AlarmManager.class, mAlarmManager::get);
mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get);
+ mProviders.put(DozeParameters.class, mDozeParameters::get);
+ mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index 0d24321..9192eed 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -22,26 +22,36 @@
import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.IActivityManager;
import android.app.INotificationManager;
+import android.app.IWallpaperManager;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
+import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.IWindowManager;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
@@ -49,6 +59,7 @@
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -58,7 +69,11 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.util.leak.LeakDetector;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
import javax.inject.Named;
+import javax.inject.Qualifier;
import javax.inject.Singleton;
import dagger.Module;
@@ -70,6 +85,12 @@
*/
@Module
public class DependencyProvider {
+ @Qualifier
+ @Documented
+ @Retention(RUNTIME)
+ public @interface MainResources {
+ // TODO: use attribute to get other, non-main resources?
+ }
@Singleton
@Provides
@@ -204,8 +225,11 @@
@Singleton
@Provides
public AutoHideController provideAutoHideController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
- return new AutoHideController(context, mainHandler);
+ @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ IWindowManager iWindowManager) {
+ return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
+ iWindowManager);
}
@Singleton
@@ -245,4 +269,48 @@
public LockPatternUtils provideLockPatternUtils(Context context) {
return new LockPatternUtils(context);
}
+
+ /** */
+ @Provides
+ public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
+ return new AmbientDisplayConfiguration(context);
+ }
+
+ /** */
+ @Provides
+ public AlwaysOnDisplayPolicy provideAlwaysOnDisplayPolicy(Context context) {
+ return new AlwaysOnDisplayPolicy(context);
+ }
+
+ /** */
+ @Provides
+ public PowerManager providePowerManager(Context context) {
+ return context.getSystemService(PowerManager.class);
+ }
+
+ /** */
+ @Provides
+ @MainResources
+ public Resources provideResources(Context context) {
+ return context.getResources();
+ }
+
+ /** */
+ @Provides
+ public IWallpaperManager provideWallPaperManager() {
+ return IWallpaperManager.Stub.asInterface(
+ ServiceManager.getService(Context.WALLPAPER_SERVICE));
+ }
+
+ /** */
+ @Provides
+ public WindowManager providesWindowManager(Context context) {
+ return context.getSystemService(WindowManager.class);
+ }
+
+ /** */
+ @Provides
+ public IActivityManager providesIActivityManager() {
+ return ActivityManager.getService();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 1c0e0b3..29a7167 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -38,6 +38,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+
/**
* Default built-in wallpaper that simply shows a static image.
*/
@@ -50,8 +52,15 @@
private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
private static final int PATIENCE_WAIT_FOR_RENDERING = 10;
private static final boolean DEBUG = true;
+ private final DozeParameters mDozeParameters;
private HandlerThread mWorker;
+ @Inject
+ public ImageWallpaper(DozeParameters dozeParameters) {
+ super();
+ mDozeParameters = dozeParameters;
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -61,7 +70,7 @@
@Override
public Engine onCreateEngine() {
- return new GLEngine(this);
+ return new GLEngine(this, mDozeParameters);
}
@Override
@@ -89,9 +98,9 @@
// This variable can only be accessed in synchronized block.
private boolean mWaitingForRendering;
- GLEngine(Context context) {
+ GLEngine(Context context, DozeParameters dozeParameters) {
mNeedTransition = ActivityManager.isHighEndGfx()
- && !DozeParameters.getInstance(context).getDisplayNeedsBlanking();
+ && !dozeParameters.getDisplayNeedsBlanking();
// We will preserve EGL context when we are in lock screen or aod
// to avoid janking in following transition, we need to release when back to home.
@@ -339,9 +348,9 @@
boolean isHighEndGfx = ActivityManager.isHighEndGfx();
out.print(prefix); out.print("isHighEndGfx="); out.println(isHighEndGfx);
- DozeParameters dozeParameters = DozeParameters.getInstance(getApplicationContext());
out.print(prefix); out.print("displayNeedsBlanking=");
- out.println(dozeParameters != null ? dozeParameters.getDisplayNeedsBlanking() : "null");
+ out.println(
+ mDozeParameters != null ? mDozeParameters.getDisplayNeedsBlanking() : "null");
out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition);
out.print(prefix); out.print("StatusBarState=");
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 50f1b44..ddbabee 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -41,6 +41,10 @@
private static final String ACTION_TURN_ON_SCREEN =
"com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
+ public LatencyTester(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
if (!Build.IS_DEBUGGABLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 3e068b0..ad20986 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -132,12 +132,15 @@
return result;
}
+ public ScreenDecorations(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mHandler = startHandlerThread();
mHandler.post(this::startOnScreenDecorationsThread);
setupStatusBarPaddingIfNeeded();
- putComponent(ScreenDecorations.class, this);
}
@VisibleForTesting
@@ -457,7 +460,7 @@
| WindowManager.LayoutParams.FLAG_SLIPPERY
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
index e761a2b..c11236e 100644
--- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
@@ -19,6 +19,7 @@
import android.app.Service;
import com.android.systemui.doze.DozeService;
+import com.android.systemui.keyguard.KeyguardService;
import dagger.Binds;
import dagger.Module;
@@ -30,8 +31,21 @@
*/
@Module
public abstract class ServiceBinder {
+ /** */
@Binds
@IntoMap
@ClassKey(DozeService.class)
public abstract Service bindDozeService(DozeService service);
+
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(ImageWallpaper.class)
+ public abstract Service bindImageWallpaper(ImageWallpaper service);
+
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardService.class)
+ public abstract Service bindKeyguardService(KeyguardService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index c54f6306..10009f5 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -59,12 +59,13 @@
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
- public SizeCompatModeActivityController() {
- this(ActivityManagerWrapper.getInstance());
+ public SizeCompatModeActivityController(Context context) {
+ this(context, ActivityManagerWrapper.getInstance());
}
@VisibleForTesting
- SizeCompatModeActivityController(ActivityManagerWrapper am) {
+ SizeCompatModeActivityController(Context context, ActivityManagerWrapper am) {
+ super(context);
am.registerTaskStackListener(new TaskStackChangeListener() {
@Override
public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
@@ -202,7 +203,7 @@
mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWinParams.format = PixelFormat.TRANSLUCENT;
- mWinParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mWinParams.setTitle(SizeCompatModeActivityController.class.getSimpleName()
+ context.getDisplayId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index b3fc69e..92fbd25 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -39,6 +39,10 @@
private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>();
+ public SliceBroadcastRelayHandler(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
if (DEBUG) Log.d(TAG, "Start");
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 8bcf057..0f7f1be 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -31,7 +31,7 @@
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast toast = Toast.makeText(context, text, duration);
toast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
return toast;
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index 30fbef6..7570037 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -26,9 +26,13 @@
import java.util.Map;
public abstract class SystemUI implements SysUiServiceProvider {
- public Context mContext;
+ protected final Context mContext;
public Map<Class<?>, Object> mComponents;
+ public SystemUI(Context context) {
+ mContext = context;
+ }
+
public abstract void start();
protected void onConfigurationChanged(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 56b5d08..91776a3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,6 +42,8 @@
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.util.NotificationChannels;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
@@ -193,18 +195,18 @@
try {
SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
if (obj == null) {
- obj = (SystemUI) Class.forName(clsName).newInstance();
+ Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
+ obj = (SystemUI) constructor.newInstance(this);
}
mServices[i] = obj;
- } catch (ClassNotFoundException ex) {
- throw new RuntimeException(ex);
- } catch (IllegalAccessException ex) {
- throw new RuntimeException(ex);
- } catch (InstantiationException ex) {
+ } catch (ClassNotFoundException
+ | NoSuchMethodException
+ | IllegalAccessException
+ | InstantiationException
+ | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
- mServices[i].mContext = this;
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
@@ -235,7 +237,7 @@
if (statusBar != null) {
plugin.setup(statusBar.getStatusBarWindow(),
statusBar.getNavigationBarView(), new Callback(plugin),
- DozeParameters.getInstance(getBaseContext()));
+ Dependency.get(DozeParameters.class));
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
index 785038f..a5a5598 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -17,10 +17,12 @@
package com.android.systemui;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.pip.PipUI;
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsModule;
import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.volume.VolumeUI;
import dagger.Binds;
import dagger.Module;
@@ -32,12 +34,25 @@
*/
@Module(includes = {RecentsModule.class})
public abstract class SystemUIBinder {
+
+ /** Inject into GarbageMonitor.Service. */
+ @Binds
+ @IntoMap
+ @ClassKey(GarbageMonitor.Service.class)
+ public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service service);
+
/** Inject into KeyguardViewMediator. */
@Binds
@IntoMap
@ClassKey(KeyguardViewMediator.class)
public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+ /** Inject into PipUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PipUI.class)
+ public abstract SystemUI bindPipUI(PipUI sysui);
+
/** Inject into PowerUI. */
@Binds
@IntoMap
@@ -50,9 +65,10 @@
@ClassKey(Recents.class)
public abstract SystemUI bindRecents(Recents sysui);
- /** Inject into GarbageMonitor.Service. */
+ /** Inject into VolumeUI. */
@Binds
@IntoMap
- @ClassKey(GarbageMonitor.Service.class)
- public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service service);
+ @ClassKey(VolumeUI.class)
+ public abstract SystemUI bindVolumeUI(VolumeUI sysui);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 530dcdc..ef7526b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -48,7 +48,6 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.volume.VolumeDialogComponent;
import java.util.function.Consumer;
@@ -161,7 +160,8 @@
StatusBarStateController statusBarStateController) {
return new NotificationIconAreaController(context, statusBar, statusBarStateController,
wakeUpCoordinator, keyguardBypassController,
- Dependency.get(NotificationMediaManager.class));
+ Dependency.get(NotificationMediaManager.class),
+ Dependency.get(DozeParameters.class));
}
public KeyguardIndicationController createKeyguardIndicationController(Context context,
@@ -169,10 +169,6 @@
return new KeyguardIndicationController(context, indicationArea, lockIcon);
}
- public VolumeDialogComponent createVolumeDialogComponent(SystemUI systemUi, Context context) {
- return new VolumeDialogComponent(systemUi, context);
- }
-
@Module
public static class ContextHolder {
private Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/VendorServices.java b/packages/SystemUI/src/com/android/systemui/VendorServices.java
index 0be6b12..13d847b 100644
--- a/packages/SystemUI/src/com/android/systemui/VendorServices.java
+++ b/packages/SystemUI/src/com/android/systemui/VendorServices.java
@@ -16,11 +16,17 @@
package com.android.systemui;
+import android.content.Context;
+
/**
* Placeholder for any vendor-specific services.
*/
public class VendorServices extends SystemUI {
+ public VendorServices(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
// no-op
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
index 739eade..6f5a17d 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -21,6 +21,7 @@
import android.os.HandlerThread;
import android.os.SystemClock;
+import androidx.annotation.Nullable;
import androidx.slice.Clock;
import com.android.internal.app.AssistUtils;
@@ -68,6 +69,7 @@
}
@Provides
+ @Nullable
static AssistHandleViewController provideAssistHandleViewController(
NavigationBarController navigationBarController) {
return navigationBarController.getAssistHandlerViewController();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 9958124..4cb1708 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -164,8 +164,11 @@
}
private void updateAssistHandleVisibility() {
- AssistHandleViewController controller = Dependency.get(NavigationBarController.class)
- .getAssistHandlerViewController();
+ NavigationBarController navigationBarController =
+ Dependency.get(NavigationBarController.class);
+ AssistHandleViewController controller =
+ navigationBarController == null
+ ? null : navigationBarController.getAssistHandlerViewController();
if (controller != null) {
controller.setAssistHintBlocked(mInvocationInProgress);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index a9359d4..f1abdb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -580,7 +580,7 @@
WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("BiometricPrompt");
lp.token = windowToken;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 4c2afb0..cdc2623 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -172,12 +172,13 @@
}
}
- public AuthController() {
- this(new Injector());
+ public AuthController(Context context) {
+ this(context, new Injector());
}
@VisibleForTesting
- AuthController(Injector injector) {
+ AuthController(Context context, Injector injector) {
+ super(context);
mInjector = injector;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 8df072e..bebaa4b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -26,7 +26,7 @@
import android.widget.TextView;
import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
/**
@@ -96,13 +96,16 @@
}
private void checkPasswordAndUnlock() {
- final byte[] password = LockPatternUtils.charSequenceToByteArray(mPasswordField.getText());
- if (password == null || password.length == 0) {
- return;
- }
+ try (LockscreenCredential password = mCredentialType == Utils.CREDENTIAL_PIN
+ ? LockscreenCredential.createPinOrNone(mPasswordField.getText())
+ : LockscreenCredential.createPasswordOrNone(mPasswordField.getText())) {
+ if (password.isNone()) {
+ return;
+ }
- mPendingLockCheck = LockPatternChecker.checkPassword(mLockPatternUtils,
- password, mUserId, this::onCredentialChecked);
+ mPendingLockCheck = LockPatternChecker.checkCredential(mLockPatternUtils,
+ password, mUserId, this::onCredentialChecked);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 6c36f82..14414a4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -22,6 +22,7 @@
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
import java.util.List;
@@ -64,11 +65,13 @@
return;
}
- mPendingLockCheck = LockPatternChecker.checkPattern(
- mLockPatternUtils,
- pattern,
- mUserId,
- this::onPatternChecked);
+ try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
+ mPendingLockCheck = LockPatternChecker.checkCredential(
+ mLockPatternUtils,
+ credential,
+ mUserId,
+ this::onPatternChecked);
+ }
}
private void onPatternChecked(boolean matched, int timeoutMs) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
index 85a4d23..b726c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
@@ -16,6 +16,9 @@
package com.android.systemui.classifier.brightline;
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+
import android.view.MotionEvent;
import java.util.Locale;
@@ -29,6 +32,7 @@
class PointerCountClassifier extends FalsingClassifier {
private static final int MAX_ALLOWED_POINTERS = 1;
+ private static final int MAX_ALLOWED_POINTERS_SWIPE_DOWN = 2;
private int mMaxPointerCount;
PointerCountClassifier(FalsingDataProvider dataProvider) {
@@ -50,6 +54,10 @@
@Override
public boolean isFalseTouch() {
+ int interactionType = getInteractionType();
+ if (interactionType == QUICK_SETTINGS || interactionType == NOTIFICATION_DRAG_DOWN) {
+ return mMaxPointerCount > MAX_ALLOWED_POINTERS_SWIPE_DOWN;
+ }
return mMaxPointerCount > MAX_ALLOWED_POINTERS;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index ca4ec6d..3f0505f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -18,6 +18,7 @@
import android.app.AlarmManager;
import android.app.Application;
+import android.app.IWallpaperManager;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -25,7 +26,6 @@
import android.os.Handler;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.dock.DockManager;
@@ -39,47 +39,71 @@
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import javax.inject.Inject;
+
public class DozeFactory {
- public DozeFactory() {
+ private final FalsingManager mFalsingManager;
+ private final DozeLog mDozeLog;
+ private final DozeParameters mDozeParameters;
+ private final BatteryController mBatteryController;
+ private final AsyncSensorManager mAsyncSensorManager;
+ private final AlarmManager mAlarmManager;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DockManager mDockManager;
+ private final IWallpaperManager mWallpaperManager;
+
+ @Inject
+ public DozeFactory(FalsingManager falsingManager, DozeLog dozeLog,
+ DozeParameters dozeParameters, BatteryController batteryController,
+ AsyncSensorManager asyncSensorManager, AlarmManager alarmManager,
+ WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DockManager dockManager, IWallpaperManager wallpaperManager) {
+ mFalsingManager = falsingManager;
+ mDozeLog = dozeLog;
+ mDozeParameters = dozeParameters;
+ mBatteryController = batteryController;
+ mAsyncSensorManager = asyncSensorManager;
+ mAlarmManager = alarmManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mDockManager = dockManager;
+ mWallpaperManager = wallpaperManager;
}
/** Creates a DozeMachine with its parts for {@code dozeService}. */
- public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager,
- DozeLog dozeLog) {
- Context context = dozeService;
- AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
- AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
- DockManager dockManager = Dependency.get(DockManager.class);
- WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
-
+ public DozeMachine assembleMachine(DozeService dozeService) {
DozeHost host = getHost(dozeService);
- AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
- DozeParameters params = DozeParameters.getInstance(context);
+ AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(dozeService);
Handler handler = new Handler();
WakeLock wakeLock = new DelayedWakeLock(handler,
- WakeLock.createPartial(context, "Doze"));
+ WakeLock.createPartial(dozeService, "Doze"));
DozeMachine.Service wrappedService = dozeService;
wrappedService = new DozeBrightnessHostForwarder(wrappedService, host);
- wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(wrappedService, params);
- wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(wrappedService,
- params);
+ wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
+ wrappedService, mDozeParameters);
+ wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
+ wrappedService, mDozeParameters);
DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
- wakefulnessLifecycle, Dependency.get(BatteryController.class), dozeLog);
+ mWakefulnessLifecycle, mBatteryController, mDozeLog);
machine.setParts(new DozeMachine.Part[]{
- new DozePauser(handler, machine, alarmManager, params.getPolicy()),
- new DozeFalsingManagerAdapter(falsingManager),
- createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
- handler, wakeLock, machine, dockManager, dozeLog),
- createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params,
- dozeLog),
- new DozeScreenState(wrappedService, handler, params, wakeLock),
- createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
- handler),
- new DozeWallpaperState(context, getBiometricUnlockController(dozeService)),
- new DozeDockHandler(context, machine, host, config, handler, dockManager),
+ new DozePauser(handler, machine, mAlarmManager, mDozeParameters.getPolicy()),
+ new DozeFalsingManagerAdapter(mFalsingManager),
+ createDozeTriggers(dozeService, mAsyncSensorManager, host, mAlarmManager, config,
+ mDozeParameters, handler, wakeLock, machine, mDockManager, mDozeLog),
+ createDozeUi(dozeService, host, wakeLock, machine, handler, mAlarmManager,
+ mDozeParameters, mDozeLog),
+ new DozeScreenState(wrappedService, handler, host, mDozeParameters, wakeLock),
+ createDozeScreenBrightness(dozeService, wrappedService, mAsyncSensorManager, host,
+ mDozeParameters, handler),
+ new DozeWallpaperState(
+ mWallpaperManager,
+ getBiometricUnlockController(dozeService),
+ mDozeParameters),
+ new DozeDockHandler(dozeService, machine, host, config, handler, mDockManager),
new DozeAuthRemover(dozeService)
});
@@ -110,7 +134,7 @@
DozeMachine machine, Handler handler, AlarmManager alarmManager,
DozeParameters params, DozeLog dozeLog) {
return new DozeUi(context, alarmManager, machine, wakeLock, host, handler, params,
- Dependency.get(KeyguardUpdateMonitor.class), dozeLog);
+ mKeyguardUpdateMonitor, dozeLog);
}
public static DozeHost getHost(DozeService service) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 07dd2cd..1a6bd60 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -63,9 +63,10 @@
void setDozeScreenBrightness(int value);
/**
- * Makes scrims black and changes animation durations.
+ * Fade out screen before switching off the display power mode.
+ * @param onDisplayOffCallback Executed when the display is black.
*/
- default void prepareForGentleWakeUp() {}
+ void prepareForGentleSleep(Runnable onDisplayOffCallback);
void onIgnoreTouchWhilePulsing(boolean ignore);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 38ee2fe..95c42fc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,6 +16,12 @@
package com.android.systemui.doze;
+import static com.android.systemui.doze.DozeMachine.State.DOZE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
+
import android.os.Handler;
import android.util.Log;
import android.view.Display;
@@ -48,21 +54,24 @@
private final Handler mHandler;
private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
private final DozeParameters mParameters;
+ private final DozeHost mDozeHost;
private int mPendingScreenState = Display.STATE_UNKNOWN;
private SettableWakeLock mWakeLock;
- public DozeScreenState(DozeMachine.Service service, Handler handler,
+ public DozeScreenState(DozeMachine.Service service, Handler handler, DozeHost host,
DozeParameters parameters, WakeLock wakeLock) {
mDozeService = service;
mHandler = handler;
mParameters = parameters;
+ mDozeHost = host;
mWakeLock = new SettableWakeLock(wakeLock, TAG);
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
int screenState = newState.screenState(mParameters);
+ mDozeHost.prepareForGentleSleep(null);
if (newState == DozeMachine.State.FINISH) {
// Make sure not to apply the screen state after DozeService was destroyed.
@@ -79,12 +88,13 @@
return;
}
- boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
- boolean pulseEnding = oldState == DozeMachine.State.DOZE_PULSE_DONE
- && newState == DozeMachine.State.DOZE_AOD;
- boolean turningOn = (oldState == DozeMachine.State.DOZE_AOD_PAUSED
- || oldState == DozeMachine.State.DOZE) && newState == DozeMachine.State.DOZE_AOD;
- boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
+ final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
+ final boolean pulseEnding = oldState == DOZE_PULSE_DONE && newState == DOZE_AOD;
+ final boolean turningOn = (oldState == DOZE_AOD_PAUSED
+ || oldState == DOZE) && newState == DOZE_AOD;
+ final boolean turningOff = (oldState == DOZE_AOD && newState == DOZE)
+ || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED);
+ final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
if (messagePending || justInitialized || pulseEnding || turningOn) {
// During initialization, we hide the navigation bar. That is however only applied after
// a traversal; setting the screen state here is immediate however, so it can happen
@@ -93,7 +103,7 @@
mPendingScreenState = screenState;
// Delay screen state transitions even longer while animations are running.
- boolean shouldDelayTransition = newState == DozeMachine.State.DOZE_AOD
+ boolean shouldDelayTransition = newState == DOZE_AOD
&& mParameters.shouldControlScreenOff() && !turningOn;
if (shouldDelayTransition) {
@@ -114,6 +124,8 @@
} else if (DEBUG) {
Log.d(TAG, "Pending display state change to " + screenState);
}
+ } else if (turningOff) {
+ mDozeHost.prepareForGentleSleep(() -> applyScreenState(screenState));
} else {
applyScreenState(screenState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 17559c9..08734d2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -22,10 +22,8 @@
import android.service.dreams.DreamService;
import android.util.Log;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.DozeServicePlugin;
import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.plugins.PluginManager;
@@ -38,18 +36,17 @@
implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> {
private static final String TAG = "DozeService";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final FalsingManager mFalsingManager;
- private final DozeLog mDozeLog;
+ private final DozeFactory mDozeFactory;
private DozeMachine mDozeMachine;
private DozeServicePlugin mDozePlugin;
private PluginManager mPluginManager;
@Inject
- public DozeService(FalsingManager falsingManager, DozeLog dozeLog) {
+ public DozeService(DozeFactory dozeFactory, PluginManager pluginManager) {
setDebug(DEBUG);
- mFalsingManager = falsingManager;
- mDozeLog = dozeLog;
+ mDozeFactory = dozeFactory;
+ mPluginManager = pluginManager;
}
@Override
@@ -62,9 +59,8 @@
finish();
return;
}
- mPluginManager = Dependency.get(PluginManager.class);
mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
- mDozeMachine = new DozeFactory().assembleMachine(this, mFalsingManager, mDozeLog);
+ mDozeMachine = mDozeFactory.assembleMachine(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 2c0ccd21..f155783 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -135,7 +135,6 @@
break;
case DOZE:
case DOZE_AOD_PAUSED:
- mHost.prepareForGentleWakeUp();
unscheduleTimeTick();
break;
case DOZE_REQUEST_PULSE:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 35c8b74..9457dc9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -17,12 +17,9 @@
package com.android.systemui.doze;
import android.app.IWallpaperManager;
-import android.content.Context;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -42,17 +39,10 @@
private final BiometricUnlockController mBiometricUnlockController;
private boolean mIsAmbientMode;
- public DozeWallpaperState(Context context,
- BiometricUnlockController biometricUnlockController) {
- this(IWallpaperManager.Stub.asInterface(
- ServiceManager.getService(Context.WALLPAPER_SERVICE)),
- biometricUnlockController,
- DozeParameters.getInstance(context));
- }
-
- @VisibleForTesting
- DozeWallpaperState(IWallpaperManager wallpaperManagerService,
- BiometricUnlockController biometricUnlockController, DozeParameters parameters) {
+ public DozeWallpaperState(
+ IWallpaperManager wallpaperManagerService,
+ BiometricUnlockController biometricUnlockController,
+ DozeParameters parameters) {
mWallpaperManagerService = wallpaperManagerService;
mBiometricUnlockController = biometricUnlockController;
mDozeParameters = parameters;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index e8ef454..c11127d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -35,6 +35,10 @@
private Extension<GlobalActions> mExtension;
private IStatusBarService mBarService;
+ public GlobalActionsComponent(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mBarService = IStatusBarService.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 7d52a9a..42f455a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -117,6 +117,10 @@
private int mState;
+ public KeyboardUI(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mContext = super.mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index b3481c5..4a33590 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -19,9 +19,13 @@
import android.os.Handler;
import android.os.Message;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Dispatches the lifecycles keyguard gets from WindowManager on the main thread.
*/
+@Singleton
public class KeyguardLifecyclesDispatcher {
static final int SCREEN_TURNING_ON = 0;
@@ -37,6 +41,7 @@
private final ScreenLifecycle mScreenLifecycle;
private final WakefulnessLifecycle mWakefulnessLifecycle;
+ @Inject
public KeyguardLifecyclesDispatcher(ScreenLifecycle screenLifecycle,
WakefulnessLifecycle wakefulnessLifecycle) {
mScreenLifecycle = screenLifecycle;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 81247cd..9f4056f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -33,25 +33,30 @@
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
-import com.android.systemui.Dependency;
import com.android.systemui.SystemUIApplication;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
public class KeyguardService extends Service {
static final String TAG = "KeyguardService";
static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
- private KeyguardViewMediator mKeyguardViewMediator;
- private KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+ private final KeyguardViewMediator mKeyguardViewMediator;
+ private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+
+ @Inject
+ public KeyguardService(KeyguardViewMediator keyguardViewMediator,
+ KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher) {
+ super();
+ mKeyguardViewMediator = keyguardViewMediator;
+ mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
+ }
@Override
public void onCreate() {
((SystemUIApplication) getApplication()).startServicesIfNeeded();
- mKeyguardViewMediator =
- ((SystemUIApplication) getApplication()).getComponent(KeyguardViewMediator.class);
- mKeyguardLifecyclesDispatcher = new KeyguardLifecyclesDispatcher(
- Dependency.get(ScreenLifecycle.class),
- Dependency.get(WakefulnessLifecycle.class));
-
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a8027c0..e0270de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -685,9 +685,8 @@
Context context,
FalsingManager falsingManager,
LockPatternUtils lockPatternUtils) {
- super();
+ super(context);
- mContext = context;
mFalsingManager = falsingManager;
mLockPatternUtils = lockPatternUtils;
@@ -795,7 +794,6 @@
synchronized (this) {
setupLocked();
}
- putComponent(KeyguardViewMediator.class, this);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index a9fe54b..4d061e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -49,6 +49,12 @@
private static final String TAG = "WorkLockActivity";
/**
+ * Add additional extra {@link com.android.settings.password.ConfirmDeviceCredentialActivity} to
+ * enable device policy management enforcement from systemui.
+ */
+ public static final String EXTRA_FROM_WORK_LOCK_ACTIVITY = "from_work_lock_activity";
+
+ /**
* Contains a {@link TaskDescription} for the activity being covered.
*/
static final String EXTRA_TASK_DESCRIPTION =
@@ -151,6 +157,7 @@
if (target != null) {
credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
+ credential.putExtra(EXTRA_FROM_WORK_LOCK_ACTIVITY, true);
}
startActivityForResult(credential, REQUEST_CODE_CONFIRM_CREDENTIALS);
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index aebadf9..4c96de2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -59,6 +59,10 @@
private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
+ public RingtonePlayer(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mAsyncPlayer.setUsesWakeLock(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 682c76c..f1e801b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.UserHandle;
@@ -30,15 +31,24 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Controls the picture-in-picture window.
*/
+@Singleton
public class PipUI extends SystemUI implements CommandQueue.Callbacks {
private BasePipManager mPipManager;
private boolean mSupportsPip;
+ @Inject
+ public PipUI(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
PackageManager pm = mContext.getPackageManager();
@@ -59,7 +69,6 @@
mPipManager.initialize(mContext);
getComponent(CommandQueue.class).addCallback(this);
- putComponent(PipUI.class, this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 8224365..3f15966 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -107,7 +107,7 @@
| LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
lp.setTitle("pip-dismiss-overlay");
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
mWindowManager.addView(mDismissView, lp);
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index a258f35..98f0b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -103,7 +103,8 @@
private final BroadcastDispatcher mBroadcastDispatcher;
@Inject
- public PowerUI(BroadcastDispatcher broadcastDispatcher) {
+ public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher) {
+ super(context);
mBroadcastDispatcher = broadcastDispatcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index ae83567..2e24403 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -28,10 +28,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -49,6 +51,8 @@
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.phone.NPVPluginManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
@@ -98,6 +102,10 @@
private BrightnessMirrorController mBrightnessMirrorController;
private View mDivider;
+ private FrameLayout mPluginFrame;
+ private final PluginManager mPluginManager;
+ private NPVPluginManager mNPVPluginManager;
+
public QSPanel(Context context) {
this(context, null);
}
@@ -106,9 +114,13 @@
this(context, attrs, null);
}
+ public QSPanel(Context context, AttributeSet attrs, DumpController dumpController) {
+ this(context, attrs, dumpController, null);
+ }
+
@Inject
public QSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
- DumpController dumpController) {
+ DumpController dumpController, PluginManager pluginManager) {
super(context, attrs);
mContext = context;
@@ -136,6 +148,15 @@
mBrightnessController = new BrightnessController(getContext(),
findViewById(R.id.brightness_slider));
mDumpController = dumpController;
+ mPluginManager = pluginManager;
+ if (mPluginManager != null && Settings.System.getInt(
+ mContext.getContentResolver(), "npv_plugin_flag", 0) == 2) {
+ mPluginFrame = (FrameLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.status_bar_expanded_plugin_frame, this, false);
+ addView(mPluginFrame);
+ mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+ }
+
}
protected void addDivider() {
@@ -377,6 +398,7 @@
if (mListening) {
refreshAllTiles();
}
+ if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
}
public void setListening(boolean listening, boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 14addb9..37743ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -2,6 +2,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -31,6 +32,9 @@
private boolean mListening;
protected int mMaxAllowedRows = 3;
+ // Prototyping with less rows
+ private final boolean mLessRows;
+
public TileLayout(Context context) {
this(context, null);
}
@@ -38,7 +42,9 @@
public TileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusableInTouchMode(true);
+ mLessRows = Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0;
updateResources();
+
}
@Override
@@ -89,6 +95,7 @@
mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
+ if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1);
if (mColumns != columns) {
mColumns = columns;
requestLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 466c808..411980b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -39,6 +39,7 @@
import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+import android.widget.Switch;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
@@ -82,6 +83,11 @@
mTile = new Tile();
updateDefaultTileAndIcon();
mServiceManager = host.getTileServices().getTileWrapper(this);
+ if (mServiceManager.isBooleanTile()) {
+ // Replace states with BooleanState
+ resetStates();
+ }
+
mService = mServiceManager.getTileService();
mServiceManager.setTileChangeListener(this);
mUser = ActivityManager.getCurrentUser();
@@ -246,8 +252,10 @@
@Override
public State newTileState() {
- State state = new State();
- return state;
+ if (mServiceManager != null && mServiceManager.isBooleanTile()) {
+ return new BooleanState();
+ }
+ return new State();
}
@Override
@@ -336,6 +344,12 @@
} else {
state.contentDescription = state.label;
}
+
+ if (state instanceof BooleanState) {
+ state.expandedAccessibilityClassName = Switch.class.getName();
+ ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
+ }
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index effea6a..f59e0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -131,6 +131,24 @@
}
/**
+ * Determines whether the associated TileService is a Boolean Tile.
+ *
+ * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this
+ * tile
+ * @see TileService#META_DATA_BOOLEAN_TILE
+ */
+ public boolean isBooleanTile() {
+ try {
+ ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+ return info.metaData != null
+ && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
* Binds just long enough to send any queued messages, then unbinds.
*/
public void flushMessagesAndUnbind() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 2a7e55f..0b4e648 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -123,6 +123,10 @@
return mStateManager.isActiveTile();
}
+ public boolean isBooleanTile() {
+ return mStateManager.isBooleanTile();
+ }
+
public void setShowingDialog(boolean dialog) {
mShowingDialog = dialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index daaee4c..1c8e451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -16,6 +16,7 @@
import android.content.Context;
import android.os.Build;
+import android.provider.Settings;
import android.util.Log;
import android.view.ContextThemeWrapper;
@@ -32,6 +33,7 @@
import com.android.systemui.qs.tiles.CastTile;
import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.ControlsTile;
import com.android.systemui.qs.tiles.DataSaverTile;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.FlashlightTile;
@@ -58,6 +60,7 @@
private final Provider<WifiTile> mWifiTileProvider;
private final Provider<BluetoothTile> mBluetoothTileProvider;
+ private final Provider<ControlsTile> mControlsTileProvider;
private final Provider<CellularTile> mCellularTileProvider;
private final Provider<DndTile> mDndTileProvider;
private final Provider<ColorInversionTile> mColorInversionTileProvider;
@@ -81,6 +84,7 @@
@Inject
public QSFactoryImpl(Provider<WifiTile> wifiTileProvider,
Provider<BluetoothTile> bluetoothTileProvider,
+ Provider<ControlsTile> controlsTileProvider,
Provider<CellularTile> cellularTileProvider,
Provider<DndTile> dndTileProvider,
Provider<ColorInversionTile> colorInversionTileProvider,
@@ -100,6 +104,7 @@
Provider<UiModeNightTile> uiModeNightTileProvider) {
mWifiTileProvider = wifiTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
+ mControlsTileProvider = controlsTileProvider;
mCellularTileProvider = cellularTileProvider;
mDndTileProvider = dndTileProvider;
mColorInversionTileProvider = colorInversionTileProvider;
@@ -138,6 +143,11 @@
return mWifiTileProvider.get();
case "bt":
return mBluetoothTileProvider.get();
+ case "controls":
+ if (Settings.System.getInt(mHost.getContext().getContentResolver(),
+ "qs_controls_tile_enabled", 0) == 1) {
+ return mControlsTileProvider.get();
+ } else return null;
case "cell":
return mCellularTileProvider.get();
case "dnd":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 681de37..e0f26cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -139,6 +139,11 @@
mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
}
+ protected final void resetStates() {
+ mState = newTileState();
+ mTmpState = newTileState();
+ }
+
@NonNull
@Override
public Lifecycle getLifecycle() {
@@ -629,6 +634,11 @@
}
}
+ /**
+ * Dumps the state of this tile along with its name.
+ *
+ * This may be used for CTS testing of tiles.
+ */
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(this.getClass().getSimpleName() + ":");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java
new file mode 100644
index 0000000..0a59618
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.HomeControlsPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import javax.inject.Inject;
+
+
+/**
+ * Temporary control test for prototyping
+ */
+public class ControlsTile extends QSTileImpl<BooleanState> {
+ private ControlsDetailAdapter mDetailAdapter;
+ private final ActivityStarter mActivityStarter;
+ private PluginManager mPluginManager;
+ private HomeControlsPlugin mPlugin;
+ private Intent mHomeAppIntent;
+
+ @Inject
+ public ControlsTile(QSHost host,
+ ActivityStarter activityStarter,
+ PluginManager pluginManager) {
+ super(host);
+ mActivityStarter = activityStarter;
+ mPluginManager = pluginManager;
+ mDetailAdapter = (ControlsDetailAdapter) createDetailAdapter();
+
+ mHomeAppIntent = new Intent(Intent.ACTION_VIEW);
+ mHomeAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mHomeAppIntent.setComponent(new ComponentName("com.google.android.apps.chromecast.app",
+ "com.google.android.apps.chromecast.app.DiscoveryActivity"));
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleSetListening(boolean listening) {
+
+ }
+
+ @Override
+ public void setDetailListening(boolean listening) {
+ if (mPlugin == null) return;
+
+ mPlugin.setVisible(listening);
+ }
+
+ @Override
+ protected void handleClick() {
+ showDetail(true);
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return mHomeAppIntent;
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
+ showDetail(true);
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return "Controls";
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.icon = ResourceIcon.get(R.drawable.ic_lightbulb_outline_gm2_24px);
+ state.label = "Controls";
+ }
+
+ @Override
+ public boolean supportsDetailView() {
+ return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return -1;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return "On";
+ } else {
+ return "Off";
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ protected DetailAdapter createDetailAdapter() {
+ mDetailAdapter = new ControlsDetailAdapter();
+ return mDetailAdapter;
+ }
+
+ private class ControlsDetailAdapter implements DetailAdapter {
+ private View mDetailView;
+ protected LinearLayout mHomeControlsLayout;
+
+ public CharSequence getTitle() {
+ return "Controls";
+ }
+
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ public boolean getToggleEnabled() {
+ return false;
+ }
+
+ public View createDetailView(Context context, View convertView, final ViewGroup parent) {
+ mHomeControlsLayout = (LinearLayout) LayoutInflater.from(context).inflate(
+ R.layout.home_controls, parent, false);
+ mHomeControlsLayout.setVisibility(View.VISIBLE);
+ mPluginManager.addPluginListener(
+ new PluginListener<HomeControlsPlugin>() {
+ @Override
+ public void onPluginConnected(HomeControlsPlugin plugin,
+ Context pluginContext) {
+ mPlugin = plugin;
+ mPlugin.sendParentGroup(mHomeControlsLayout);
+ mPlugin.setVisible(true);
+ }
+
+ @Override
+ public void onPluginDisconnected(HomeControlsPlugin plugin) {
+
+ }
+ }, HomeControlsPlugin.class, false);
+ return mHomeControlsLayout;
+ }
+
+ public Intent getSettingsIntent() {
+ return mHomeAppIntent;
+ }
+
+ public void setToggleState(boolean state) {
+
+ }
+
+ public int getMetricsCategory() {
+ return -1;
+ }
+
+ public boolean hasHeader() {
+ return false;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index e0ae8ed..3fc1398 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,6 +101,7 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
+ private final PipUI mPipUI;
private SysUiState mSysUiState;
private final Handler mHandler;
private final NavigationBarController mNavBarController;
@@ -361,8 +362,7 @@
}
long token = Binder.clearCallingIdentity();
try {
- final PipUI component = SysUiServiceProvider.getComponent(mContext, PipUI.class);
- component.setShelfHeight(visible, shelfHeight);
+ mPipUI.setShelfHeight(visible, shelfHeight);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -479,8 +479,9 @@
public OverviewProxyService(Context context, DeviceProvisionedController provisionController,
NavigationBarController navBarController, NavigationModeController navModeController,
StatusBarWindowController statusBarWinController,
- SysUiState sysUiState) {
+ SysUiState sysUiState, PipUI pipUI) {
mContext = context;
+ mPipUI = pipUI;
mHandler = new Handler();
mNavBarController = navBarController;
mStatusBarWinController = statusBarWinController;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index a1b4a93..0a8264b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.provider.Settings;
@@ -37,7 +38,8 @@
private final RecentsImplementation mImpl;
@Inject
- public Recents(RecentsImplementation impl) {
+ public Recents(Context context, RecentsImplementation impl) {
+ super(context);
mImpl = impl;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index c1ce163..aa64449 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -497,7 +497,7 @@
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
flags,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("RecentsOnboarding");
lp.gravity = gravity;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 0f277ca..2d1c087 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -128,7 +128,7 @@
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("ScreenPinningConfirmation");
lp.gravity = Gravity.FILL;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 07675e2..df9791d 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -19,6 +19,7 @@
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
import android.util.Log;
@@ -52,6 +53,10 @@
protected final long SC_DOCK_LEFT = META_MASK | KeyEvent.KEYCODE_LEFT_BRACKET;
protected final long SC_DOCK_RIGHT = META_MASK | KeyEvent.KEYCODE_RIGHT_BRACKET;
+ public ShortcutKeyDispatcher(Context context) {
+ super(context);
+ }
+
/**
* Registers a shortcut key to window manager.
* @param shortcutCode packed representation of shortcut key code and meta information
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index cd2074f..c8b2b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -19,6 +19,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
import android.util.Log;
@@ -50,6 +51,10 @@
private boolean mHomeStackResizable = false;
private ForcedResizableInfoActivityController mForcedResizableController;
+ public Divider(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mWindowManager = new DividerWindowManager(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 36e04fe..d6a8f90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -1101,6 +1101,10 @@
// Need this class since CommandQueue already extends IStatusBar.Stub, so CommandQueueStart
// is needed so it can extend SystemUI.
public static class CommandQueueStart extends SystemUI {
+ public CommandQueueStart(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
putComponent(CommandQueue.class, new CommandQueue(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 16cdfaa..5144a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -152,7 +152,9 @@
// Dependency problem.
AutoHideController autoHideController = isOnDefaultDisplay
? Dependency.get(AutoHideController.class)
- : new AutoHideController(context, mHandler);
+ : new AutoHideController(context, mHandler,
+ Dependency.get(NotificationRemoteInputManager.class),
+ Dependency.get(IWindowManager.class));
navBar.setAutoHideController(autoHideController);
navBar.restoreSystemUiVisibilityState();
mNavigationBars.append(displayId, navBar);
@@ -231,11 +233,13 @@
}
/** @return {@link NavigationBarFragment} on the default display. */
+ @Nullable
public NavigationBarFragment getDefaultNavigationBarFragment() {
return mNavigationBars.get(DEFAULT_DISPLAY);
}
/** @return {@link AssistHandleViewController} (only on the default display). */
+ @Nullable
public AssistHandleViewController getAssistHandlerViewController() {
NavigationBarFragment navBar = getDefaultNavigationBarFragment();
return navBar == null ? null : navBar.getAssistHandlerViewController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index a5b7fa7..f284f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -31,6 +31,7 @@
import android.app.PendingIntent;
import android.app.SynchronousUserSwitchObserver;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -60,7 +61,7 @@
import java.util.List;
-/** The clsss to show notification(s) of instant apps. This may show multiple notifications on
+/** The class to show notification(s) of instant apps. This may show multiple notifications on
* splitted screen.
*/
public class InstantAppNotifier extends SystemUI
@@ -74,7 +75,9 @@
private boolean mDockedStackExists;
private KeyguardStateController mKeyguardStateController;
- public InstantAppNotifier() {}
+ public InstantAppNotifier(Context context) {
+ super(context);
+ }
@Override
public void start() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 2f67f90..8a23e37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -37,10 +37,10 @@
@Singleton
class NotificationWakeUpCoordinator @Inject constructor(
- private val mContext: Context,
private val mHeadsUpManagerPhone: HeadsUpManagerPhone,
private val statusBarStateController: StatusBarStateController,
- private val bypassController: KeyguardBypassController)
+ private val bypassController: KeyguardBypassController,
+ private val dozeParameters: DozeParameters)
: OnHeadsUpChangedListener, StatusBarStateController.StateListener,
PanelExpansionListener {
@@ -67,7 +67,6 @@
private var mVisibilityAmount = 0.0f
private var mLinearVisibilityAmount = 0.0f
private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>()
- private val mDozeParameters: DozeParameters
private var pulseExpanding: Boolean = false
private val wakeUpListeners = arrayListOf<WakeUpListener>()
private var state: Int = StatusBarState.KEYGUARD
@@ -146,7 +145,6 @@
init {
mHeadsUpManagerPhone.addListener(this)
statusBarStateController.addCallback(this)
- mDozeParameters = DozeParameters.getInstance(mContext)
addListener(object : WakeUpListener {
override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
if (isFullyHidden && mNotificationsVisibleForExpansion) {
@@ -377,7 +375,7 @@
}
private fun shouldAnimateVisibility() =
- mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking()
+ dozeParameters.getAlwaysOn() && !dozeParameters.getDisplayNeedsBlanking()
interface WakeUpListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7bbe818..9817825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -473,8 +473,7 @@
private int mHeadsUpInset;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private NotificationIconAreaController mIconAreaController;
- private final NotificationLockscreenUserManager mLockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
private final Rect mTmpRect = new Rect();
private final NotificationEntryManager mEntryManager =
Dependency.get(NotificationEntryManager.class);
@@ -497,8 +496,7 @@
private NotificationPanelView mNotificationPanel;
private final ShadeController mShadeController = Dependency.get(ShadeController.class);
- private final NotificationGutsManager
- mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
+ private final NotificationGutsManager mNotificationGutsManager;
private final NotificationSectionsManager mSectionsManager;
private boolean mAnimateBottomOnLayout;
private float mLastSentAppear;
@@ -518,6 +516,8 @@
HeadsUpManagerPhone headsUpManager,
KeyguardBypassController keyguardBypassController,
FalsingManager falsingManager,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ NotificationGutsManager notificationGutsManager,
NotificationSectionsFeatureManager sectionsFeatureManager) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -526,6 +526,8 @@
mRoundnessManager = notificationRoundnessManager;
+ mLockscreenUserManager = notificationLockscreenUserManager;
+ mNotificationGutsManager = notificationGutsManager;
mHeadsUpManager = headsUpManager;
mHeadsUpManager.addListener(mRoundnessManager);
mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 5912cd7..175d072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -29,7 +29,6 @@
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -68,12 +67,14 @@
};
@Inject
- public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
+ public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ IWindowManager iWindowManager) {
mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class);
mCommandQueue.addCallback(this);
mHandler = handler;
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
- mWindowManagerService = Dependency.get(IWindowManager.class);
+ mRemoteInputManager = notificationRemoteInputManager;
+ mWindowManagerService = iWindowManager;
mDisplayId = context.getDisplayId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 7cbdfb0..548afd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -128,6 +128,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final StatusBarWindowController mStatusBarWindowController;
private final Context mContext;
@@ -146,31 +147,33 @@
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- public BiometricUnlockController(Context context,
+ public BiometricUnlockController(
+ Context context,
DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator,
ScrimController scrimController,
StatusBar statusBar,
KeyguardStateController keyguardStateController, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController keyguardBypassController) {
+ KeyguardBypassController keyguardBypassController,
+ DozeParameters dozeParameters) {
this(context, dozeScrimController, keyguardViewMediator, scrimController, statusBar,
keyguardStateController, handler, keyguardUpdateMonitor,
context.getResources()
.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze),
- keyguardBypassController);
+ keyguardBypassController, dozeParameters);
}
@VisibleForTesting
- protected BiometricUnlockController(Context context,
- DozeScrimController dozeScrimController, KeyguardViewMediator keyguardViewMediator,
- ScrimController scrimController, StatusBar statusBar,
- KeyguardStateController keyguardStateController, Handler handler,
+ protected BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
+ KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
+ StatusBar statusBar, KeyguardStateController keyguardStateController, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor, int wakeUpDelay,
- KeyguardBypassController keyguardBypassController) {
+ KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mUpdateMonitor = keyguardUpdateMonitor;
+ mDozeParameters = dozeParameters;
mUpdateMonitor.registerCallback(this);
mMediaManager = Dependency.get(NotificationMediaManager.class);
Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
@@ -284,7 +287,7 @@
}
// During wake and unlock, we need to draw black before waking up to avoid abrupt
// brightness changes due to display state transitions.
- boolean alwaysOnEnabled = DozeParameters.getInstance(mContext).getAlwaysOn();
+ boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
boolean delayWakeUp = mode == MODE_WAKE_AND_UNLOCK && alwaysOnEnabled && mWakeUpDelay > 0;
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
index 0d62703..78ea5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
@@ -100,6 +100,7 @@
event.getY() - mActivationY);
}
if (withinDoubleTapSlop) {
+ makeInactive();
if (!mDoubleTapListener.onDoubleTap()) {
return false;
}
@@ -134,6 +135,7 @@
if (mActivated) {
mActivated = false;
mActivationListener.onActiveChanged(false);
+ mView.removeCallbacks(mTapTimeoutRunnable);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index bb6a38e..28dac87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.phone;
-import android.content.Context;
+import android.content.res.Resources;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
import android.os.SystemProperties;
@@ -25,7 +25,7 @@
import android.util.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
+import com.android.systemui.DependencyProvider;
import com.android.systemui.R;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
@@ -33,9 +33,13 @@
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Retrieve doze information
*/
+@Singleton
public class DozeParameters implements TunerService.Tunable,
com.android.systemui.plugins.statusbar.DozeParameters {
private static final int MAX_DURATION = 60 * 1000;
@@ -44,35 +48,33 @@
public static final boolean FORCE_BLANKING =
SystemProperties.getBoolean("debug.force_blanking", false);
- private static DozeParameters sInstance;
-
- private final Context mContext;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final PowerManager mPowerManager;
private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
+ private final Resources mResources;
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
- public static DozeParameters getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new DozeParameters(context);
- }
- return sInstance;
- }
-
- @VisibleForTesting
- protected DozeParameters(Context context) {
- mContext = context.getApplicationContext();
- mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
- mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(mContext);
+ @Inject
+ protected DozeParameters(
+ @DependencyProvider.MainResources Resources resources,
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
+ PowerManager powerManager,
+ TunerService tunerService) {
+ mResources = resources;
+ mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+ mAlwaysOnPolicy = alwaysOnDisplayPolicy;
mControlScreenOffAnimation = !getDisplayNeedsBlanking();
- mPowerManager = mContext.getSystemService(PowerManager.class);
+ mPowerManager = powerManager;
mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
- Dependency.get(TunerService.class).addTunable(this, Settings.Secure.DOZE_ALWAYS_ON,
+ tunerService.addTunable(
+ this,
+ Settings.Secure.DOZE_ALWAYS_ON,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
}
@@ -95,7 +97,7 @@
}
public boolean getDozeSuspendDisplayStateSupported() {
- return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
+ return mResources.getBoolean(R.bool.doze_suspend_display_state_supported);
}
public int getPulseDuration() {
@@ -103,7 +105,7 @@
}
public float getScreenBrightnessDoze() {
- return mContext.getResources().getInteger(
+ return mResources.getInteger(
com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
}
@@ -173,7 +175,7 @@
* @return {@code true} if screen needs to be completely black before a power transition.
*/
public boolean getDisplayNeedsBlanking() {
- return FORCE_BLANKING || !FORCE_NO_BLANKING && mContext.getResources().getBoolean(
+ return FORCE_BLANKING || !FORCE_NO_BLANKING && mResources.getBoolean(
com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
@@ -195,24 +197,20 @@
}
private boolean getBoolean(String propName, int resId) {
- return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
+ return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
}
private int getInt(String propName, int resId) {
- int value = SystemProperties.getInt(propName, mContext.getResources().getInteger(resId));
+ int value = SystemProperties.getInt(propName, mResources.getInteger(resId));
return MathUtils.constrain(value, 0, MAX_DURATION);
}
- private String getString(String propName, int resId) {
- return SystemProperties.get(propName, mContext.getString(resId));
- }
-
public int getPulseVisibleDurationExtended() {
return 2 * getPulseVisibleDuration();
}
public boolean doubleTapReportsTouchCoordinates() {
- return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
+ return mResources.getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index ca7c227..442c089 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -276,7 +276,7 @@
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
mEdgePanelLp.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mEdgePanelLp.setTitle(TAG + mDisplayId);
mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
mEdgePanelLp.windowAnimations = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
index deb314b..a784984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -82,7 +82,7 @@
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("FloatingRotationButton");
switch (mWindowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_0:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index da62d9b..ce96005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -98,9 +98,10 @@
View statusbarView,
SysuiStatusBarStateController statusBarStateController,
KeyguardBypassController keyguardBypassController,
+ KeyguardStateController keyguardStateController,
NotificationWakeUpCoordinator wakeUpCoordinator) {
this(notificationIconAreaController, headsUpManager, statusBarStateController,
- keyguardBypassController, wakeUpCoordinator,
+ keyguardBypassController, wakeUpCoordinator, keyguardStateController,
statusbarView.findViewById(R.id.heads_up_status_bar_view),
statusbarView.findViewById(R.id.notification_stack_scroller),
statusbarView.findViewById(R.id.notification_panel),
@@ -116,6 +117,7 @@
StatusBarStateController stateController,
KeyguardBypassController bypassController,
NotificationWakeUpCoordinator wakeUpCoordinator,
+ KeyguardStateController keyguardStateController,
HeadsUpStatusBarView headsUpStatusBarView,
NotificationStackScrollLayout stackScroller,
NotificationPanelView panelView,
@@ -160,7 +162,7 @@
mWakeUpCoordinator = wakeUpCoordinator;
wakeUpCoordinator.addListener(this);
mCommandQueue = getComponent(headsUpStatusBarView.getContext(), CommandQueue.class);
- mKeyguardStateController = Dependency.get(KeyguardStateController.class);
+ mKeyguardStateController = keyguardStateController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a7e7f08..c6d051d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -29,6 +29,7 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import androidx.collection.ArraySet;
@@ -390,7 +391,12 @@
}
private void updateRegionForNotch(Region region) {
- DisplayCutout cutout = mStatusBarWindowView.getRootWindowInsets().getDisplayCutout();
+ WindowInsets windowInsets = mStatusBarWindowView.getRootWindowInsets();
+ if (windowInsets == null) {
+ Log.w(TAG, "StatusBarWindowView is not attached.");
+ return;
+ }
+ DisplayCutout cutout = windowInsets.getDisplayCutout();
if (cutout == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index cac3304..d95d2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -132,7 +132,6 @@
private ActivityStarter mActivityStarter;
private KeyguardStateController mKeyguardStateController;
- private LockPatternUtils mLockPatternUtils;
private FlashlightController mFlashlightController;
private PreviewInflater mPreviewInflater;
private AccessibilityController mAccessibilityController;
@@ -231,7 +230,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mLockPatternUtils = new LockPatternUtils(mContext);
mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
new ActivityIntentHelper(mContext));
mPreviewContainer = findViewById(R.id.preview_container);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 9804f9f..ae18833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -1212,8 +1212,11 @@
setClipChildren(shouldClip);
setClipToPadding(shouldClip);
- AssistHandleViewController controller = Dependency.get(NavigationBarController.class)
- .getAssistHandlerViewController();
+ NavigationBarController navigationBarController =
+ Dependency.get(NavigationBarController.class);
+ AssistHandleViewController controller =
+ navigationBarController == null
+ ? null : navigationBarController.getAssistHandlerViewController();
if (controller != null) {
controller.setBottomOffset(insets.getSystemWindowInsetBottom());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 1a3560e..1a37520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -80,11 +80,14 @@
private boolean mAodIconsVisible;
private boolean mIsPulsing;
- public NotificationIconAreaController(Context context, StatusBar statusBar,
+ public NotificationIconAreaController(
+ Context context,
+ StatusBar statusBar,
StatusBarStateController statusBarStateController,
NotificationWakeUpCoordinator wakeUpCoordinator,
KeyguardBypassController keyguardBypassController,
- NotificationMediaManager notificationMediaManager) {
+ NotificationMediaManager notificationMediaManager,
+ DozeParameters dozeParameters) {
mStatusBar = statusBar;
mContrastColorUtil = ContrastColorUtil.getInstance(context);
mContext = context;
@@ -92,7 +95,7 @@
mStatusBarStateController = statusBarStateController;
mStatusBarStateController.addCallback(this);
mMediaManager = notificationMediaManager;
- mDozeParameters = DozeParameters.getInstance(mContext);
+ mDozeParameters = dozeParameters;
mWakeUpCoordinator = wakeUpCoordinator;
wakeUpCoordinator.addListener(this);
mBypassController = keyguardBypassController;
@@ -533,8 +536,7 @@
}
public void appearAodIcons() {
- DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
- if (dozeParameters.shouldControlScreenOff()) {
+ if (mDozeParameters.shouldControlScreenOff()) {
mAodIcons.setTranslationY(-mAodIconAppearTranslation);
mAodIcons.setAlpha(0);
animateInAodIconTranslation();
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 00736b7..89051cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -43,6 +43,7 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
@@ -89,6 +90,7 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -144,6 +146,7 @@
* Fling until QS is completely hidden.
*/
public static final int FLING_HIDE = 2;
+ private final DozeParameters mDozeParameters;
private double mQqsSplitFraction;
@@ -452,17 +455,17 @@
@Inject
public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
InjectionInflationController injectionInflationController,
- NotificationWakeUpCoordinator coordinator,
- PulseExpansionHandler pulseExpansionHandler,
+ NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
DynamicPrivacyController dynamicPrivacyController,
- KeyguardBypassController bypassController,
- FalsingManager falsingManager,
- PluginManager pluginManager,
- ShadeController shadeController,
+ KeyguardBypassController bypassController, FalsingManager falsingManager,
+ PluginManager pluginManager, ShadeController shadeController,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
- DozeLog dozeLog) {
- super(context, attrs, falsingManager, dozeLog);
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController, DozeLog dozeLog,
+ DozeParameters dozeParameters) {
+ super(context, attrs, falsingManager, dozeLog, keyguardStateController,
+ (SysuiStatusBarStateController) statusBarStateController);
setWillNotDraw(!DEBUG);
mInjectionInflationController = injectionInflationController;
mFalsingManager = falsingManager;
@@ -475,6 +478,7 @@
mCommandQueue = getComponent(context, CommandQueue.class);
mDisplayId = context.getDisplayId();
mPulseExpansionHandler = pulseExpansionHandler;
+ mDozeParameters = dozeParameters;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
mQs.animateHeaderSlidingOut();
@@ -537,7 +541,10 @@
mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
mLastOrientation = getResources().getConfiguration().orientation;
mPluginFrame = findViewById(R.id.plugin_frame);
- mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+ if (Settings.System.getInt(
+ mContext.getContentResolver(), "npv_plugin_flag", 0) == 1) {
+ mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+ }
initBottomArea();
@@ -774,7 +781,7 @@
mPluginFrame.setLayoutParams(lp);
}
- mNPVPluginManager.replaceFrameLayout(mPluginFrame);
+ if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
}
private void initBottomArea() {
@@ -804,8 +811,10 @@
int oldMaxHeight = mQsMaxExpansionHeight;
if (mQs != null) {
mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
- mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
- mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
+ mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+ }
mQsMaxExpansionHeight = mQs.getDesiredHeight();
mNotificationStackScroller.setMaxTopPadding(
mQsMaxExpansionHeight + mQsNotificationTopPadding);
@@ -1911,9 +1920,11 @@
mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
updateEmptyShadeView();
- mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
- ? View.VISIBLE
- : View.INVISIBLE);
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
+ ? View.VISIBLE
+ : View.INVISIBLE);
+ }
mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
&& !mStackScrollerOverscrolling && mQsScrimEnabled
? View.VISIBLE
@@ -1971,7 +1982,9 @@
float qsExpansionFraction = getQsExpansionFraction();
mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
- mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+ }
mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
}
@@ -2392,7 +2405,7 @@
appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
}
startHeight = -mQs.getQsMinExpansionHeight();
- startHeight -= mNPVPluginManager.getHeight();
+ if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
}
float translation = MathUtils.lerp(startHeight, 0,
Math.min(1.0f, appearAmount))
@@ -2536,7 +2549,7 @@
mKeyguardStatusBar.setListening(listening);
if (mQs == null) return;
mQs.setListening(listening);
- mNPVPluginManager.setListening(listening);
+ if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
}
@Override
@@ -3421,9 +3434,8 @@
public void setPulsing(boolean pulsing) {
mPulsing = pulsing;
- DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
- final boolean animatePulse = !dozeParameters.getDisplayNeedsBlanking()
- && dozeParameters.getAlwaysOn();
+ final boolean animatePulse = !mDozeParameters.getDisplayNeedsBlanking()
+ && mDozeParameters.getAlwaysOn();
if (animatePulse) {
mAnimateNextPositionUpdate = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 432d636..e8e5e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -44,7 +44,6 @@
import com.android.systemui.R;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -144,10 +143,8 @@
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
- protected final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
- protected final SysuiStatusBarStateController mStatusBarStateController =
- (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
+ protected final KeyguardStateController mKeyguardStateController;
+ protected final SysuiStatusBarStateController mStatusBarStateController;
protected void onExpandingFinished() {
mBar.onExpandingFinished();
@@ -206,8 +203,11 @@
}
public PanelView(Context context, AttributeSet attrs, FalsingManager falsingManager,
- DozeLog dozeLog) {
+ DozeLog dozeLog, KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController) {
super(context, attrs);
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f /* maxLengthSeconds */,
0.6f /* speedUpFactor */);
mFlingAnimationUtilsClosing = new FlingAnimationUtils(context, 0.5f /* maxLengthSeconds */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5ba19cc..e7d896c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -258,7 +258,7 @@
final ScrimState oldState = mState;
mState = state;
- Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.getIndex());
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal());
if (mCallback != null) {
mCallback.onCancelled();
@@ -519,22 +519,6 @@
}
/**
- * Set front scrim to black, cancelling animations, in order to prepare to fade them
- * away once the display turns on.
- */
- public void prepareForGentleWakeUp() {
- if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
- mInFrontAlpha = 1f;
- mInFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
- mAnimateChange = false;
- updateScrims();
- mAnimateChange = true;
- mAnimationDuration = ANIMATION_DURATION_LONG;
- }
- }
-
- /**
* If the lock screen sensor is active.
*/
public void setWakeLockScreenSensorActive(boolean active) {
@@ -595,6 +579,8 @@
setScrimAlpha(mScrimInFront, mInFrontAlpha);
setScrimAlpha(mScrimBehind, mBehindAlpha);
setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+ // The animation could have all already finished, let's call onFinished just in case
+ onFinished();
dispatchScrimsVisible();
}
@@ -693,9 +679,9 @@
@Override
public void onAnimationEnd(Animator animation) {
+ scrim.setTag(TAG_KEY_ANIM, null);
onFinished(lastCallback);
- scrim.setTag(TAG_KEY_ANIM, null);
dispatchScrimsVisible();
if (!mDeferFinishedListener && mOnAnimationFinished != null) {
@@ -759,9 +745,9 @@
}
private void onFinished(Callback callback) {
- if (!hasReachedFinalState(mScrimBehind)
- || !hasReachedFinalState(mScrimInFront)
- || !hasReachedFinalState(mScrimForBubble)) {
+ if (isAnimating(mScrimBehind)
+ || isAnimating(mScrimInFront)
+ || isAnimating(mScrimForBubble)) {
if (callback != null && callback != mCallback) {
// Since we only notify the callback that we're finished once everything has
// finished, we need to make sure that any changing callbacks are also invoked
@@ -794,11 +780,6 @@
}
}
- private boolean hasReachedFinalState(ScrimView scrim) {
- return scrim.getViewAlpha() == getCurrentScrimAlpha(scrim)
- && scrim.getTint() == getCurrentScrimTint(scrim);
- }
-
private boolean isAnimating(View scrim) {
return scrim.getTag(TAG_KEY_ANIM) != null;
}
@@ -854,10 +835,7 @@
} else {
// update the alpha directly
updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
- onFinished();
}
- } else {
- onFinished();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 7463c7c..e0597159 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -30,12 +30,30 @@
/**
* Initial state.
*/
- UNINITIALIZED(-1),
+ UNINITIALIZED,
+
+ /**
+ * When turned off by sensors (prox, presence.)
+ */
+ OFF {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mFrontTint = Color.BLACK;
+ mBehindTint = previousState.mBehindTint;
+ mBubbleTint = previousState.mBubbleTint;
+
+ mFrontAlpha = 1f;
+ mBehindAlpha = previousState.mBehindAlpha;
+ mBubbleAlpha = previousState.mBubbleAlpha;
+
+ mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
+ }
+ },
/**
* On the lock screen.
*/
- KEYGUARD(0) {
+ KEYGUARD {
@Override
public void prepare(ScrimState previousState) {
mBlankScreen = false;
@@ -65,7 +83,7 @@
/**
* Showing password challenge on the keyguard.
*/
- BOUNCER(1) {
+ BOUNCER {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
@@ -77,7 +95,7 @@
/**
* Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
*/
- BOUNCER_SCRIMMED(2) {
+ BOUNCER_SCRIMMED {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = 0;
@@ -89,7 +107,7 @@
/**
* Changing screen brightness from quick settings.
*/
- BRIGHTNESS_MIRROR(3) {
+ BRIGHTNESS_MIRROR {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = 0;
@@ -101,7 +119,7 @@
/**
* Always on display or screen off.
*/
- AOD(4) {
+ AOD {
@Override
public void prepare(ScrimState previousState) {
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
@@ -136,7 +154,7 @@
/**
* When phone wakes up because you received a notification.
*/
- PULSING(5) {
+ PULSING {
@Override
public void prepare(ScrimState previousState) {
mFrontAlpha = mAodFrontScrimAlpha;
@@ -164,7 +182,7 @@
/**
* Unlocked on top of an app (launcher or any other activity.)
*/
- UNLOCKED(6) {
+ UNLOCKED {
@Override
public void prepare(ScrimState previousState) {
// State that UI will sync to.
@@ -201,7 +219,7 @@
/**
* Unlocked with a bubble expanded.
*/
- BUBBLE_EXPANDED(7) {
+ BUBBLE_EXPANDED {
@Override
public void prepare(ScrimState previousState) {
mFrontTint = Color.TRANSPARENT;
@@ -237,17 +255,12 @@
DozeParameters mDozeParameters;
boolean mDisplayRequiresBlanking;
boolean mWallpaperSupportsAmbientMode;
- int mIndex;
boolean mHasBackdrop;
boolean mLaunchingAffordanceWithPreview;
boolean mWakeLockScreenSensorActive;
boolean mKeyguardFadingAway;
long mKeyguardFadingAwayDuration;
- ScrimState(int index) {
- mIndex = index;
- }
-
public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
DozeParameters dozeParameters) {
mScrimInFront = scrimInFront;
@@ -262,10 +275,6 @@
public void prepare(ScrimState previousState) {
}
- public int getIndex() {
- return mIndex;
- }
-
public float getFrontAlpha() {
return mFrontAlpha;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2b80d22..c092f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -387,6 +387,7 @@
private final ConfigurationController mConfigurationController;
private final StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
private final NotifLog mNotifLog;
+ private final DozeParameters mDozeParameters;
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -497,8 +498,7 @@
WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
- final boolean imageWallpaperInAmbient =
- !DozeParameters.getInstance(mContext).getDisplayNeedsBlanking();
+ final boolean imageWallpaperInAmbient = !mDozeParameters.getDisplayNeedsBlanking();
// If WallpaperInfo is null, it must be ImageWallpaper.
final boolean supportsAmbientMode = deviceSupportsAodWallpaper
&& ((info == null && imageWallpaperInAmbient)
@@ -620,6 +620,7 @@
@Inject
public StatusBar(
+ Context context,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -673,7 +674,9 @@
ConfigurationController configurationController,
StatusBarWindowController statusBarWindowController,
StatusBarWindowViewController.Builder statusBarWindowViewControllerBuilder,
- NotifLog notifLog) {
+ NotifLog notifLog,
+ DozeParameters dozeParameters) {
+ super(context);
mLightBarController = lightBarController;
mAutoHideController = autoHideController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -728,6 +731,7 @@
mStatusBarWindowController = statusBarWindowController;
mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder;
mNotifLog = notifLog;
+ mDozeParameters = dozeParameters;
mBubbleExpandListener =
(isExpanding, key) -> {
@@ -748,7 +752,7 @@
KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
if (sliceProvider != null) {
sliceProvider.initDependencies(mMediaManager, mStatusBarStateController,
- mKeyguardBypassController, DozeParameters.getInstance(mContext));
+ mKeyguardBypassController, mDozeParameters);
} else {
Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies");
}
@@ -959,7 +963,7 @@
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow,
mStatusBarStateController, mKeyguardBypassController,
- mWakeUpCoordinator);
+ mKeyguardStateController, mWakeUpCoordinator);
mHeadsUpAppearanceController.readFrom(oldController);
mStatusBarWindowViewController.setStatusBarView(mStatusBarView);
updateAreThereNotifications();
@@ -1030,13 +1034,12 @@
if (mStatusBarWindow != null) {
mStatusBarWindowViewController.onScrimVisibilityChanged(scrimsVisible);
}
- }, DozeParameters.getInstance(mContext),
+ }, mDozeParameters,
mContext.getSystemService(AlarmManager.class),
mKeyguardStateController);
mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
mHeadsUpManager, mNotificationIconAreaController, mScrimController);
- mDozeScrimController = new DozeScrimController(DozeParameters.getInstance(context),
- mDozeLog);
+ mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog);
BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1171,7 +1174,7 @@
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
- mNotificationAlertingManager, rowBinder);
+ mNotificationAlertingManager, rowBinder, mKeyguardStateController);
mNotificationListController =
new NotificationListController(
@@ -1341,7 +1344,7 @@
mBiometricUnlockController = new BiometricUnlockController(mContext,
mDozeScrimController, mKeyguardViewMediator,
mScrimController, this, mKeyguardStateController, new Handler(),
- mKeyguardUpdateMonitor, mKeyguardBypassController);
+ mKeyguardUpdateMonitor, mKeyguardBypassController, mDozeParameters);
putComponent(BiometricUnlockController.class, mBiometricUnlockController);
mStatusBarKeyguardViewManager = mKeyguardViewMediator.registerStatusBar(this,
getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
@@ -3569,8 +3572,7 @@
mDozing = isDozing;
// Collapse the notification panel if open
- boolean dozingAnimated = mDozingRequested
- && DozeParameters.getInstance(mContext).shouldControlScreenOff();
+ boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff();
mNotificationPanel.resetViews(dozingAnimated);
updateQsExpansionEnabled();
@@ -3600,7 +3602,6 @@
private void updateKeyguardState() {
mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
- mKeyguardStateController.isMethodSecure(),
mStatusBarKeyguardViewManager.isOccluded());
}
@@ -3827,7 +3828,7 @@
*/
private void updateNotificationPanelTouchState() {
boolean goingToSleepWithoutAnimation = isGoingToSleep()
- && !DozeParameters.getInstance(mContext).shouldControlScreenOff();
+ && !mDozeParameters.shouldControlScreenOff();
boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation;
mNotificationPanel.setTouchAndAnimationDisabled(disabled);
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
@@ -4017,6 +4018,13 @@
} else if (isPulsing()) {
mScrimController.transitionTo(ScrimState.PULSING,
mDozeScrimController.getScrimCallback());
+ } else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
+ mScrimController.transitionTo(ScrimState.OFF, new ScrimController.Callback() {
+ @Override
+ public void onFinished() {
+ mDozeServiceHost.executePendingScreenOffCallback();
+ }
+ });
} else if (mDozing && !unlocking) {
mScrimController.transitionTo(ScrimState.AOD);
} else if (mIsKeyguard && !unlocking) {
@@ -4043,6 +4051,7 @@
private boolean mAnimateWakeup;
private boolean mAnimateScreenOff;
private boolean mIgnoreTouchWhilePulsing;
+ private Runnable mPendingScreenOffCallback;
@VisibleForTesting
boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
"persist.sysui.wake_performs_auth", true);
@@ -4265,8 +4274,33 @@
}
@Override
- public void prepareForGentleWakeUp() {
- mScrimController.prepareForGentleWakeUp();
+ public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+ if (onDisplayOffCallback != null) {
+ Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
+ }
+ mPendingScreenOffCallback = onDisplayOffCallback;
+ updateScrimController();
+ }
+
+ /**
+ * When the dozing host is waiting for scrims to fade out to change the display state.
+ */
+ boolean hasPendingScreenOffCallback() {
+ return mPendingScreenOffCallback != null;
+ }
+
+ /**
+ * Executes an nullifies the pending display state callback.
+ *
+ * @see #hasPendingScreenOffCallback()
+ * @see #prepareForGentleSleep(Runnable)
+ */
+ void executePendingScreenOffCallback() {
+ if (mPendingScreenOffCallback == null) {
+ return;
+ }
+ mPendingScreenOffCallback.run();
+ mPendingScreenOffCallback = null;
}
private void dispatchTap(View view, float x, float y) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 75b0cdc..8683586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -33,6 +33,8 @@
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -61,8 +63,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import androidx.annotation.VisibleForTesting;
-
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -301,8 +301,7 @@
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowController.setKeyguardShowing(true);
- mKeyguardStateController.notifyKeyguardState(
- mShowing, mKeyguardStateController.isMethodSecure(),
+ mKeyguardStateController.notifyKeyguardState(mShowing,
mKeyguardStateController.isOccluded());
reset(true /* hideBouncerWhenShowing */);
StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
@@ -545,7 +544,7 @@
public void hide(long startTime, long fadeoutDuration) {
mShowing = false;
mKeyguardStateController.notifyKeyguardState(mShowing,
- mKeyguardStateController.isMethodSecure(), mKeyguardStateController.isOccluded());
+ mKeyguardStateController.isOccluded());
launchPendingWakeupAction();
if (Dependency.get(KeyguardUpdateMonitor.class).needsSlowUnlockTransition()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 3e0c268..f4a26ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -89,8 +89,7 @@
private final ShadeController mShadeController = Dependency.get(ShadeController.class);
private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
- private final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
+ private final KeyguardStateController mKeyguardStateController;
private final NotificationViewHierarchyManager mViewHierarchyManager =
Dependency.get(NotificationViewHierarchyManager.class);
private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -139,8 +138,10 @@
ActivityLaunchAnimator activityLaunchAnimator,
DynamicPrivacyController dynamicPrivacyController,
NotificationAlertingManager notificationAlertingManager,
- NotificationRowBinderImpl notificationRowBinder) {
+ NotificationRowBinderImpl notificationRowBinder,
+ KeyguardStateController keyguardStateController) {
mContext = context;
+ mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 9a281ce..1def89b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -35,7 +35,6 @@
import android.view.ViewParent;
import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -58,19 +57,16 @@
public class StatusBarRemoteInputCallback implements Callback, Callbacks,
StatusBarStateController.StateListener {
- private final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
- private final SysuiStatusBarStateController mStatusBarStateController =
- (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
- private final NotificationLockscreenUserManager mLockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+ private final KeyguardStateController mKeyguardStateController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final ActivityStarter mActivityStarter;
+ private final ShadeController mShadeController;
private final Context mContext;
private final ActivityIntentHelper mActivityIntentHelper;
private final NotificationGroupManager mGroupManager;
private View mPendingWorkRemoteInputView;
private View mPendingRemoteInputView;
- private final ShadeController mShadeController = Dependency.get(ShadeController.class);
private KeyguardManager mKeyguardManager;
private final CommandQueue mCommandQueue;
private int mDisabled2;
@@ -80,10 +76,19 @@
/**
*/
@Inject
- public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager) {
+ public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter, ShadeController shadeController) {
mContext = context;
mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
+ mLockscreenUserManager = notificationLockscreenUserManager;
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
+ mShadeController = shadeController;
+ mActivityStarter = activityStarter;
mStatusBarStateController.addCallback(this);
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mCommandQueue = getComponent(context, CommandQueue.class);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 724b462..ca7a936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -21,7 +21,6 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -39,8 +38,6 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -94,24 +91,14 @@
private final ArrayList<WeakReference<StatusBarWindowCallback>>
mCallbacks = Lists.newArrayList();
- private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+ private final SysuiColorExtractor mColorExtractor;
@Inject
- public StatusBarWindowController(Context context,
- StatusBarStateController statusBarStateController,
- ConfigurationController configurationController,
- KeyguardBypassController keyguardBypassController) {
- this(context, context.getSystemService(WindowManager.class), ActivityManager.getService(),
- DozeParameters.getInstance(context), statusBarStateController,
- configurationController, keyguardBypassController);
- }
-
- @VisibleForTesting
public StatusBarWindowController(Context context, WindowManager windowManager,
IActivityManager activityManager, DozeParameters dozeParameters,
StatusBarStateController statusBarStateController,
ConfigurationController configurationController,
- KeyguardBypassController keyguardBypassController) {
+ KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor) {
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
@@ -120,6 +107,7 @@
mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
mLpChanged = new LayoutParams();
mKeyguardBypassController = keyguardBypassController;
+ mColorExtractor = colorExtractor;
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
((SysuiStatusBarStateController) statusBarStateController)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
index f21085e..fd3f9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
@@ -37,14 +37,17 @@
import com.android.systemui.R;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
@@ -88,7 +91,10 @@
ShadeController shadeController,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
- DozeLog dozeLog) {
+ KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController,
+ DozeLog dozeLog,
+ DozeParameters dozeParameters) {
mView = view;
mFalsingManager = falsingManager;
@@ -106,7 +112,10 @@
shadeController,
notificationLockscreenUserManager,
notificationEntryManager,
- dozeLog);
+ keyguardStateController,
+ statusBarStateController,
+ dozeLog,
+ dozeParameters);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
notificationPanelView.setVisibility(View.INVISIBLE);
@@ -472,10 +481,13 @@
private final FalsingManager mFalsingManager;
private final PluginManager mPluginManager;
private final TunerService mTunerService;
+ private final KeyguardStateController mKeyguardStateController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
private ShadeController mShadeController;
private final NotificationLockscreenUserManager mNotificationLockScreenUserManager;
private final NotificationEntryManager mNotificationEntryManager;
private final DozeLog mDozeLog;
+ private final DozeParameters mDozeParameters;
private StatusBarWindowView mView;
@Inject
@@ -490,7 +502,10 @@
TunerService tunerService,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
- DozeLog dozeLog) {
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController,
+ DozeLog dozeLog,
+ DozeParameters dozeParameters) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -501,7 +516,10 @@
mTunerService = tunerService;
mNotificationLockScreenUserManager = notificationLockscreenUserManager;
mNotificationEntryManager = notificationEntryManager;
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mDozeLog = dozeLog;
+ mDozeParameters = dozeParameters;
}
/**
@@ -537,7 +555,10 @@
mShadeController,
mNotificationLockScreenUserManager,
mNotificationEntryManager,
- mDozeLog);
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mDozeLog,
+ mDozeParameters);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index ce929b7..44be6bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,10 +93,10 @@
public static void setShowForAllUsers(Dialog dialog, boolean show) {
if (show) {
dialog.getWindow().getAttributes().privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
} else {
dialog.getWindow().getAttributes().privateFlags &=
- ~WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ ~WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index aefe201..692c34c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -146,7 +146,7 @@
/** **/
default void notifyKeyguardDoneFading() {}
/** **/
- default void notifyKeyguardState(boolean showing, boolean methodSecure, boolean occluded) {}
+ default void notifyKeyguardState(boolean showing, boolean occluded) {}
/**
* Callback for authentication events.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 392094d..1cb2bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -25,11 +25,12 @@
import android.os.Build;
import android.os.Trace;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import java.io.FileDescriptor;
@@ -42,25 +43,22 @@
/**
*/
@Singleton
-public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback
- implements KeyguardStateController, Dumpable {
+public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
private static final boolean DEBUG_AUTH_WITH_ADB = false;
private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
- private final Context mContext;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
- new LockedStateInvalidator();
+ new UpdateMonitorCallback();
private boolean mCanDismissLockScreen;
private boolean mShowing;
private boolean mSecure;
private boolean mOccluded;
- private boolean mListening;
private boolean mKeyguardFadingAway;
private long mKeyguardFadingAwayDelay;
private long mKeyguardFadingAwayDuration;
@@ -75,10 +73,10 @@
/**
*/
@Inject
- public KeyguardStateControllerImpl(Context context) {
- mContext = context;
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mLockPatternUtils = new LockPatternUtils(context);
+ public KeyguardStateControllerImpl(Context context,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
update(true /* updateAlways */);
@@ -104,19 +102,12 @@
if (!mCallbacks.contains(callback)) {
mCallbacks.add(callback);
}
- if (mCallbacks.size() != 0 && !mListening) {
- mListening = true;
- mKeyguardUpdateMonitor.registerCallback(this);
- }
}
@Override
public void removeCallback(@NonNull Callback callback) {
Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
- if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
- mListening = false;
- mKeyguardUpdateMonitor.removeCallback(this);
- }
+ mCallbacks.remove(callback);
}
@Override
@@ -140,19 +131,13 @@
}
@Override
- public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
- if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
+ public void notifyKeyguardState(boolean showing, boolean occluded) {
+ if (mShowing == showing && mOccluded == occluded) return;
mShowing = showing;
- mSecure = secure;
mOccluded = occluded;
notifyKeyguardChanged();
}
- @Override
- public void onTrustChanged(int userId) {
- notifyKeyguardChanged();
- }
-
private void notifyKeyguardChanged() {
Trace.beginSection("KeyguardStateController#notifyKeyguardChanged");
// Copy the list to allow removal during callback.
@@ -191,7 +176,8 @@
setKeyguardFadingAway(false);
}
- private void update(boolean updateAlways) {
+ @VisibleForTesting
+ void update(boolean updateAlways) {
Trace.beginSection("KeyguardStateController#update");
int user = KeyguardUpdateMonitor.getCurrentUser();
boolean secure = mLockPatternUtils.isSecure(user);
@@ -201,7 +187,7 @@
boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user);
boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen
- || trustManaged != mTrustManaged
+ || trustManaged != mTrustManaged || mTrusted != trusted
|| mFaceAuthEnabled != faceAuthEnabled;
if (changed || updateAlways) {
mSecure = secure;
@@ -284,7 +270,7 @@
pw.println(" mFaceAuthEnabled: " + mFaceAuthEnabled);
}
- private class LockedStateInvalidator extends KeyguardUpdateMonitorCallback {
+ private class UpdateMonitorCallback extends KeyguardUpdateMonitorCallback {
@Override
public void onUserSwitchComplete(int userId) {
update(false /* updateAlways */);
@@ -293,6 +279,7 @@
@Override
public void onTrustChanged(int userId) {
update(false /* updateAlways */);
+ notifyKeyguardChanged();
}
@Override
@@ -327,11 +314,6 @@
}
@Override
- public void onScreenTurnedOff() {
- update(false /* updateAlways */);
- }
-
- @Override
public void onKeyguardVisibilityChanged(boolean showing) {
update(false /* updateAlways */);
}
@@ -340,5 +322,5 @@
public void onBiometricsCleared() {
update(false /* alwaysUpdate */);
}
- };
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 43795dc..cca9479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -23,12 +23,16 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.Editable;
@@ -53,8 +57,13 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.core.view.inputmethod.EditorInfoCompat;
+import androidx.core.view.inputmethod.InputConnectionCompat;
+import androidx.core.view.inputmethod.InputContentInfoCompat;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -65,6 +74,7 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LightBarController;
+import java.util.HashMap;
import java.util.function.Consumer;
/**
@@ -88,6 +98,8 @@
private RemoteInputController mController;
private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
+ private IStatusBarService mStatusBarManagerService;
+
private NotificationEntry mEntry;
private boolean mRemoved;
@@ -103,6 +115,8 @@
public RemoteInputView(Context context, AttributeSet attrs) {
super(context, attrs);
mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class);
+ mStatusBarManagerService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
@Override
@@ -128,7 +142,7 @@
if (isSoftImeEvent || isKeyboardEnterKey) {
if (mEditText.length() > 0) {
- sendRemoteInput();
+ sendRemoteInput(prepareRemoteInputFromText());
}
// Consume action to prevent IME from closing.
return true;
@@ -141,7 +155,7 @@
mEditText.mRemoteInputView = this;
}
- private void sendRemoteInput() {
+ protected Intent prepareRemoteInputFromText() {
Bundle results = new Bundle();
results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -153,6 +167,25 @@
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
}
+ return fillInIntent;
+ }
+
+ protected Intent prepareRemoteInputFromData(String contentType, Uri data) {
+ HashMap<String, Uri> results = new HashMap<>();
+ results.put(contentType, data);
+ try {
+ mStatusBarManagerService.grantInlineReplyUriPermission(
+ mEntry.notification.getKey(), data);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to grant URI permissions:" + e.getMessage(), e);
+ }
+ Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results);
+
+ return fillInIntent;
+ }
+
+ private void sendRemoteInput(Intent intent) {
mEditText.setEnabled(false);
mSendButton.setVisibility(INVISIBLE);
mProgressBar.setVisibility(VISIBLE);
@@ -176,7 +209,7 @@
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_SEND,
mEntry.notification.getPackageName());
try {
- mPendingIntent.send(mContext, 0, fillInIntent);
+ mPendingIntent.send(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
Log.i(TAG, "Unable to send remote input result", e);
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_FAIL,
@@ -195,7 +228,9 @@
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
v.mController = controller;
v.mEntry = entry;
- v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
+ UserHandle user = computeTextOperationUser(entry.notification.getUser());
+ v.mEditText.mUser = user;
+ v.mEditText.setTextOperationUser(user);
v.setTag(VIEW_TAG);
return v;
@@ -204,7 +239,7 @@
@Override
public void onClick(View v) {
if (v == mSendButton) {
- sendRemoteInput();
+ sendRemoteInput(prepareRemoteInputFromText());
}
}
@@ -518,6 +553,7 @@
private RemoteInputView mRemoteInputView;
boolean mShowImeOnInputConnection;
private LightBarController mLightBarController;
+ UserHandle mUser;
public RemoteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -617,11 +653,47 @@
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ String[] allowedDataTypes = mRemoteInputView.mRemoteInput.getAllowedDataTypes()
+ .toArray(new String[0]);
+ EditorInfoCompat.setContentMimeTypes(outAttrs, allowedDataTypes);
final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
- if (mShowImeOnInputConnection && inputConnection != null) {
+ final InputConnectionCompat.OnCommitContentListener callback =
+ new InputConnectionCompat.OnCommitContentListener() {
+ @Override
+ public boolean onCommitContent(
+ InputContentInfoCompat inputContentInfoCompat, int i,
+ Bundle bundle) {
+ Uri contentUri = inputContentInfoCompat.getContentUri();
+ ClipDescription description = inputContentInfoCompat.getDescription();
+ String mimeType = null;
+ if (description != null && description.getMimeTypeCount() > 0) {
+ mimeType = description.getMimeType(0);
+ }
+ if (mimeType != null) {
+ Intent dataIntent = mRemoteInputView.prepareRemoteInputFromData(
+ mimeType, contentUri);
+ mRemoteInputView.sendRemoteInput(dataIntent);
+ }
+ return true;
+ }
+ };
+
+ InputConnection ic = InputConnectionCompat.createWrapper(
+ inputConnection, outAttrs, callback);
+
+ Context userContext = null;
+ try {
+ userContext = mContext.createPackageContextAsUser(
+ mContext.getPackageName(), 0, mUser);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to create user context:" + e.getMessage(), e);
+ }
+
+ if (mShowImeOnInputConnection && ic != null) {
+ Context targetContext = userContext != null ? userContext : getContext();
final InputMethodManager imm =
- getContext().getSystemService(InputMethodManager.class);
+ targetContext.getSystemService(InputMethodManager.class);
if (imm != null) {
// onCreateInputConnection is called by InputMethodManager in the middle of
// setting up the connection to the IME; wait with requesting the IME until that
@@ -636,7 +708,7 @@
}
}
- return inputConnection;
+ return ic;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
new file mode 100644
index 0000000..a601e9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
@@ -0,0 +1,8 @@
+# Android TV Core Framework
+rgl@google.com
+valiiftime@google.com
+galinap@google.com
+patrikf@google.com
+robhor@google.com
+sergeynv@google.com
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index b80b6d5..c2ed7df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -36,6 +36,10 @@
*/
public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
+ public TvStatusBar(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
putComponent(TvStatusBar.class, this);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 89aa797..9a58a35 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -61,6 +61,10 @@
private ThemeOverlayManager mThemeManager;
private UserManager mUserManager;
+ public ThemeOverlayController(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
if (DEBUG) Log.d(TAG, "Start");
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index ff5bd03..11885c5 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -60,6 +60,10 @@
private NotificationManager mNotificationManager;
private StorageManager mStorageManager;
+ public StorageNotification(Context context) {
+ super(context);
+ }
+
private static class MoveInfo {
public int moveId;
public Bundle extras;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
index 0a3e34e..fd99ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.hardware.usb.IUsbManager;
@@ -63,6 +64,7 @@
mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
mResolveInfo = (ResolveInfo) intent.getParcelableExtra("rinfo");
+ String packageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);
PackageManager packageManager = getPackageManager();
String appName = mResolveInfo.loadLabel(packageManager).toString();
@@ -74,8 +76,20 @@
mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName,
- mDevice.getProductName());
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ boolean hasRecordPermission =
+ PermissionChecker.checkPermissionForPreflight(
+ this, android.Manifest.permission.RECORD_AUDIO, -1, uid,
+ packageName)
+ == android.content.pm.PackageManager.PERMISSION_GRANTED;
+ boolean isAudioCaptureDevice = mDevice.getHasAudioCapture();
+ boolean useRecordWarning = isAudioCaptureDevice && !hasRecordPermission;
+
+ int strID = useRecordWarning
+ ? R.string.usb_device_confirm_prompt_warn
+ : R.string.usb_device_confirm_prompt;
+
+ ap.mMessage = getString(strID, appName, mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index f35af90..8c60747 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -38,6 +38,10 @@
public static String BATTERY = "BAT";
public static String HINTS = "HNT";
+ public NotificationChannels(Context context) {
+ super(context);
+ }
+
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
final NotificationChannel batteryChannel = new NotificationChannel(BATTERY,
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 2d5ebc4..63db755 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -526,11 +526,13 @@
}
/** */
+ @Singleton
public static class Service extends SystemUI implements Dumpable {
private final GarbageMonitor mGarbageMonitor;
@Inject
- public Service(GarbageMonitor garbageMonitor) {
+ public Service(Context context, GarbageMonitor garbageMonitor) {
+ super(context);
mGarbageMonitor = garbageMonitor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index dcd0c58..2224c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -139,6 +139,12 @@
@Override
protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener cannot be null");
+ }
+ if (sensor == null) {
+ throw new IllegalArgumentException("sensor cannot be null");
+ }
mHandler.post(() -> {
if (!mInner.requestTriggerSensor(listener, sensor)) {
Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index d2f185a..25a5139 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,7 +27,6 @@
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dependency;
-import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginDependencyProvider;
@@ -40,9 +39,13 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Implementation of VolumeComponent backed by the new volume dialog.
*/
+@Singleton
public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
VolumeDialogControllerImpl.UserActivityListener{
@@ -54,12 +57,12 @@
public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
- private final SystemUI mSysui;
protected final Context mContext;
private final VolumeDialogControllerImpl mController;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS | ActivityInfo.CONFIG_UI_MODE);
+ private final KeyguardViewMediator mKeyguardViewMediator;
private VolumeDialog mDialog;
private VolumePolicy mVolumePolicy = new VolumePolicy(
DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
@@ -68,9 +71,10 @@
400 // vibrateToSilentDebounce
);
- public VolumeDialogComponent(SystemUI sysui, Context context) {
- mSysui = sysui;
+ @Inject
+ public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator) {
mContext = context;
+ mKeyguardViewMediator = keyguardViewMediator;
mController = (VolumeDialogControllerImpl) Dependency.get(VolumeDialogController.class);
mController.setUserActivityListener(this);
// Allow plugins to reference the VolumeDialogController.
@@ -133,10 +137,7 @@
@Override
public void onUserActivity() {
- final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
- if (kvm != null) {
- kvm.userActivity();
- }
+ mKeyguardViewMediator.userActivity();
}
private void applyConfiguration() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index f8cf793..b7431397 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -16,18 +16,22 @@
package com.android.systemui.volume;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.qs.tiles.DndTile;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
public class VolumeUI extends SystemUI {
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
@@ -37,6 +41,12 @@
private boolean mEnabled;
private VolumeDialogComponent mVolumeComponent;
+ @Inject
+ public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
+ super(context);
+ mVolumeComponent = volumeDialogComponent;
+ }
+
@Override
public void start() {
boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
@@ -45,8 +55,6 @@
mEnabled = enableVolumeUi || enableSafetyWarning;
if (!mEnabled) return;
- mVolumeComponent = SystemUIFactory.getInstance()
- .createVolumeDialogComponent(this, mContext);
mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
putComponent(VolumeComponent.class, getVolumeComponent());
setDefaultVolumeController();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 38537fd..1dd4863 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -144,7 +144,7 @@
mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
new CharSequence[]{}, false, new int[]{});
- when(mTelephonyManager.getMaxPhoneCount()).thenReturn(3);
+ when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true);
// This should not start listening on any of the real dependencies but will test that
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index a07f25a..364ee66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -30,6 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.Assert;
@@ -50,9 +51,10 @@
@Before
public void setUp() throws Exception {
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
Context context = getContext();
- mRow = new NotificationTestHelper(context).createRow();
+ mRow = new NotificationTestHelper(context, mDependency).createRow();
mCallback = mock(ExpandHelper.Callback.class);
mExpandHelper = new ExpandHelper(context, mCallback, 10, 100);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 64ab060..c338d70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -102,7 +102,7 @@
when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager);
- mScreenDecorations = new ScreenDecorations() {
+ mScreenDecorations = new ScreenDecorations(mContext) {
@Override
public void start() {
super.start();
@@ -126,7 +126,6 @@
mTestableLooper.processAllMessages();
}
};
- mScreenDecorations.mContext = mContext;
mScreenDecorations.mComponents = mContext.getComponents();
reset(mTunerService);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
index 3ea7150..06999bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
@@ -58,13 +58,12 @@
MockitoAnnotations.initMocks(this);
doReturn(true).when(mMockButton).show();
- mController = new SizeCompatModeActivityController(mMockAm) {
+ mController = new SizeCompatModeActivityController(mContext, mMockAm) {
@Override
RestartActivityButton createRestartButton(Context context) {
return mMockButton;
};
};
- mController.mContext = mContext;
ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
ArgumentCaptor.forClass(TaskStackChangeListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
index 19e1a5c..a766885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
@@ -35,6 +35,7 @@
import com.android.settingslib.SliceBroadcastRelay;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -44,6 +45,14 @@
public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
private static final String TEST_ACTION = "com.android.systemui.action.TEST_ACTION";
+ private SliceBroadcastRelayHandler mRelayHandler;
+ private Context mSpyContext;
+ @Before
+ public void setup() {
+ mSpyContext = spy(mContext);
+
+ mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext);
+ }
@Test
public void testRegister() {
@@ -52,8 +61,6 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
@@ -63,8 +70,8 @@
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri);
- relayHandler.handleIntent(intent);
- verify(relayHandler.mContext).registerReceiver(any(), eq(value));
+ mRelayHandler.handleIntent(intent);
+ verify(mSpyContext).registerReceiver(any(), eq(value));
}
@Test
@@ -74,8 +81,6 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
@@ -84,14 +89,14 @@
IntentFilter value = new IntentFilter(TEST_ACTION);
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
- relayHandler.handleIntent(intent);
+ mRelayHandler.handleIntent(intent);
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value));
+ verify(mSpyContext).registerReceiver(relay.capture(), eq(value));
intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
- relayHandler.handleIntent(intent);
- verify(relayHandler.mContext).unregisterReceiver(eq(relay.getValue()));
+ mRelayHandler.handleIntent(intent);
+ verify(mSpyContext).unregisterReceiver(eq(relay.getValue()));
}
@Test
@@ -101,12 +106,10 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
Intent intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
- relayHandler.handleIntent(intent);
+ mRelayHandler.handleIntent(intent);
// No crash
}
@@ -118,9 +121,6 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
-
Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
intent.putExtra(SliceBroadcastRelay.EXTRA_RECEIVER,
@@ -128,10 +128,10 @@
IntentFilter value = new IntentFilter(TEST_ACTION);
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
- relayHandler.handleIntent(intent);
+ mRelayHandler.handleIntent(intent);
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value));
- relay.getValue().onReceive(relayHandler.mContext, new Intent(TEST_ACTION));
+ verify(mSpyContext).registerReceiver(relay.capture(), eq(value));
+ relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION));
verify(Receiver.sReceiver, timeout(2000)).onReceive(any(), any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index dcdb5c3..e1eb3b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -34,6 +34,7 @@
import android.app.ActivityManager;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.biometrics.Authenticator;
@@ -98,8 +99,7 @@
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
- mAuthController = new TestableAuthController(new MockInjector());
- mAuthController.mContext = context;
+ mAuthController = new TestableAuthController(context, new MockInjector());
mAuthController.mComponents = mContext.getComponents();
mAuthController.start();
@@ -404,8 +404,8 @@
private int mBuildCount = 0;
private Bundle mLastBiometricPromptBundle;
- public TestableAuthController(Injector injector) {
- super(injector);
+ TestableAuthController(Context context, Injector injector) {
+ super(context, injector);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 5a2b5e3..3472573 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -56,8 +56,10 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -141,6 +143,10 @@
private BubbleController.BubbleExpandListener mBubbleExpandListener;
@Mock
private PendingIntent mDeleteIntent;
+ @Mock
+ private SysuiColorExtractor mColorExtractor;
+ @Mock
+ ColorExtractor.GradientColors mGradientColors;
private BubbleData mBubbleData;
@@ -150,15 +156,16 @@
mStatusBarView = new FrameLayout(mContext);
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
mContext.addMockSystemService(FaceManager.class, mFaceManager);
+ when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
// Bubbles get added to status bar window view
mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
+ mConfigurationController, mKeyguardBypassController, mColorExtractor);
mStatusBarWindowController.add(mStatusBarView, 120 /* height */);
// Need notifications for bubbles
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
mNonBubbleNotifRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 392a7cb..96ee079 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -99,7 +99,7 @@
@Before
public void setUp() throws Exception {
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
MockitoAnnotations.initMocks(this);
mEntryA1 = createBubbleEntry(1, "a1", "package.a");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
index d011e48..3ba5d1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.classifier.brightline;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -42,6 +44,7 @@
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
mDataProvider = new FalsingDataProvider(displayMetrics);
+ mDataProvider.setInteractionType(UNLOCK);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
index 341b74b..96b2028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.classifier.brightline;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -74,4 +76,21 @@
motionEvent.recycle();
assertThat(mClassifier.isFalseTouch(), is(true));
}
+
+ @Test
+ public void testPass_multiPointerDragDown() {
+ MotionEvent.PointerProperties[] pointerProperties =
+ MotionEvent.PointerProperties.createArray(2);
+ pointerProperties[0].id = 0;
+ pointerProperties[1].id = 1;
+ MotionEvent.PointerCoords[] pointerCoords = MotionEvent.PointerCoords.createArray(2);
+ MotionEvent motionEvent = MotionEvent.obtain(
+ 1, 1, MotionEvent.ACTION_DOWN, 2, pointerProperties, pointerCoords, 0, 0, 0, 0, 0,
+ 0,
+ 0, 0);
+ mClassifier.onTouchEvent(motionEvent);
+ motionEvent.recycle();
+ getDataProvider().setInteractionType(QUICK_SETTINGS);
+ assertThat(mClassifier.isFalseTouch(), is(false));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 1ce0172..98ec4594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -20,13 +20,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.Instrumentation;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.Looper;
@@ -34,7 +32,6 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -45,23 +42,24 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class DozeDockHandlerTest extends SysuiTestCase {
- private DozeDockHandler mDockHandler;
+ @Mock
private DozeMachine mMachine;
- private DozeHostFake mHost;
+ @Mock
+ private DozeHost mHost;
private AmbientDisplayConfiguration mConfig;
- private Instrumentation mInstrumentation;
private DockManagerFake mDockManagerFake;
+ private DozeDockHandler mDockHandler;
@Before
public void setUp() throws Exception {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mMachine = mock(DozeMachine.class);
- mHost = spy(new DozeHostFake());
+ MockitoAnnotations.initMocks(this);
mConfig = DozeConfigurationUtil.createMockConfig();
doReturn(false).when(mConfig).alwaysOnEnabled(anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
deleted file mode 100644
index abfa755..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.doze;
-
-import android.annotation.NonNull;
-
-/**
- * A rudimentary fake for DozeHost.
- */
-class DozeHostFake implements DozeHost {
- Callback callback;
- boolean pulseExtended;
- boolean animateWakeup;
- boolean animateScreenOff;
- boolean dozing;
- float doubleTapX;
- float doubleTapY;
- float aodDimmingScrimOpacity;
-
- @Override
- public void addCallback(@NonNull Callback callback) {
- this.callback = callback;
- }
-
- @Override
- public void removeCallback(@NonNull Callback callback) {
- this.callback = null;
- }
-
- @Override
- public void startDozing() {
- dozing = true;
- }
-
- @Override
- public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
- throw new RuntimeException("not implemented");
- }
-
- @Override
- public void stopDozing() {
- dozing = false;
- }
-
- @Override
- public void dozeTimeTick() {
- // Nothing to do in here. Real host would just update the UI.
- }
-
- @Override
- public boolean isPowerSaveActive() {
- return false;
- }
-
- @Override
- public boolean isPulsingBlocked() {
- return false;
- }
-
- @Override
- public boolean isProvisioned() {
- return false;
- }
-
- @Override
- public boolean isBlockingDoze() {
- return false;
- }
-
- @Override
- public void onIgnoreTouchWhilePulsing(boolean ignore) {
- }
-
- @Override
- public void extendPulse(int reason) {
- pulseExtended = true;
- }
-
- @Override
- public void stopPulsing() {}
-
- @Override
- public void setAnimateWakeup(boolean animateWakeup) {
- this.animateWakeup = animateWakeup;
- }
-
- @Override
- public void setAnimateScreenOff(boolean animateScreenOff) {
- this.animateScreenOff = animateScreenOff;
- }
-
- @Override
- public void onSlpiTap(float x, float y) {
- doubleTapX = y;
- doubleTapY = y;
- }
-
- @Override
- public void setDozeScreenBrightness(int value) {
- }
-
- @Override
- public void setAodDimmingScrim(float scrimOpacity) {
- aodDimmingScrimOpacity = scrimOpacity;
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index aa62e9a..316b891 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -30,6 +30,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import android.content.Intent;
import android.os.PowerManager;
@@ -45,6 +50,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -55,22 +62,27 @@
static final int[] SENSOR_TO_OPACITY = new int[]{-1, 10, 0, 0, 0};
DozeServiceFake mServiceFake;
- DozeScreenBrightness mScreen;
FakeSensorManager.FakeGenericSensor mSensor;
FakeSensorManager mSensorManager;
- DozeHostFake mHostFake;
+ @Mock
+ DozeHost mDozeHost;
+ DozeScreenBrightness mScreen;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS, DEFAULT_BRIGHTNESS,
UserHandle.USER_CURRENT);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mDozeHost).prepareForGentleSleep(any());
mServiceFake = new DozeServiceFake();
- mHostFake = new DozeHostFake();
mSensorManager = new FakeSensorManager(mContext);
mSensor = mSensorManager.getFakeLightSensor();
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- mSensor.getSensor(), mHostFake, null /* handler */,
+ mSensor.getSensor(), mDozeHost, null /* handler */,
DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY,
true /* debuggable */);
}
@@ -173,7 +185,7 @@
@Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- null /* sensor */, mHostFake, null /* handler */,
+ null /* sensor */, mDozeHost, null /* handler */,
DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY,
true /* debuggable */);
@@ -203,26 +215,7 @@
mSensor.sendSensorEvent(0);
assertEquals(1, mServiceFake.screenBrightness);
- assertEquals(10/255f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
- }
-
- @Test
- public void pausingAod_softBlanks() throws Exception {
- mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-
- mSensor.sendSensorEvent(2);
-
- mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
- mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
-
- mSensor.sendSensorEvent(0);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
-
- mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(10f / 255f));
}
@Test
@@ -232,8 +225,9 @@
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+ reset(mDozeHost);
mSensor.sendSensorEvent(1);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(1f));
}
@Test
@@ -241,11 +235,12 @@
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.transitionTo(DOZE_AOD, DOZE);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(1f));
+ reset(mDozeHost);
mScreen.transitionTo(DOZE, DOZE_AOD);
mSensor.sendSensorEvent(2);
- assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(0f));
}
@Test
@@ -260,11 +255,10 @@
mSensor.sendSensorEvent(0);
+ reset(mDozeHost);
mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
-
mSensor.sendSensorEvent(2);
-
- assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(0f));
}
@Test
@@ -273,11 +267,11 @@
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mSensor.sendSensorEvent(2);
-
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
- mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
- assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ reset(mDozeHost);
+ mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
+ verify(mDozeHost).setAodDimmingScrim(eq(0f));
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index bfd448a..b92f173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -18,6 +18,8 @@
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
import static com.android.systemui.doze.DozeMachine.State.FINISH;
@@ -30,6 +32,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Looper;
@@ -46,6 +51,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -53,12 +59,14 @@
@SmallTest
public class DozeScreenStateTest extends SysuiTestCase {
- DozeServiceFake mServiceFake;
- DozeScreenState mScreen;
- FakeHandler mHandlerFake;
+ private DozeServiceFake mServiceFake;
+ private FakeHandler mHandlerFake;
@Mock
- DozeParameters mDozeParameters;
- WakeLockFake mWakeLock;
+ private DozeHost mDozeHost;
+ @Mock
+ private DozeParameters mDozeParameters;
+ private WakeLockFake mWakeLock;
+ private DozeScreenState mScreen;
@Before
public void setUp() throws Exception {
@@ -68,7 +76,8 @@
mServiceFake = new DozeServiceFake();
mHandlerFake = new FakeHandler(Looper.getMainLooper());
mWakeLock = new WakeLockFake();
- mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeParameters, mWakeLock);
+ mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
+ mWakeLock);
}
@Test
@@ -183,4 +192,20 @@
assertThat(mWakeLock.isHeld(), is(false));
}
+ @Test
+ public void test_animatesPausing() {
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ doAnswer(invocation -> null).when(mDozeHost).prepareForGentleSleep(captor.capture());
+ mHandlerFake.setMode(QUEUEING);
+
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD_PAUSING);
+ mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+
+ mHandlerFake.dispatchQueuedMessages();
+ verify(mDozeHost).prepareForGentleSleep(eq(captor.getValue()));
+ captor.getValue().run();
+ assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+ }
+
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index e5ae6d5..777fec7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -50,14 +51,22 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
public class DozeTriggersTest extends SysuiTestCase {
- private DozeTriggers mTriggers;
+
+ @Mock
private DozeMachine mMachine;
- private DozeHostFake mHost;
+ @Mock
+ private DozeHost mHost;
+ @Mock
+ private AlarmManager mAlarmManager;
+ private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
private Sensor mTapSensor;
private DockManager mDockManagerFake;
@@ -65,9 +74,7 @@
@Before
public void setUp() throws Exception {
- mMachine = mock(DozeMachine.class);
- AlarmManager alarmManager = mock(AlarmManager.class);
- mHost = spy(new DozeHostFake());
+ MockitoAnnotations.initMocks(this);
AmbientDisplayConfiguration config = DozeConfigurationUtil.createMockConfig();
DozeParameters parameters = DozeConfigurationUtil.createMockParameters();
mSensors = spy(new FakeSensorManager(mContext));
@@ -78,7 +85,7 @@
new AsyncSensorManager(mSensors, null, new Handler());
mProximitySensor = new FakeProximitySensor(getContext(), asyncSensorManager);
- mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters,
+ mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters,
asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
mDockManagerFake, mProximitySensor, mock(DozeLog.class));
waitForSensorManager();
@@ -87,19 +94,21 @@
@Test
public void testOnNotification_stillWorksAfterOneFailedProxCheck() throws Exception {
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+ ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
+ doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
clearInvocations(mMachine);
mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 1));
- mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners();
verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt());
- mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
waitForSensorManager();
mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
mProximitySensor.alertListeners();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 4958c64..8f4de3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -682,8 +682,7 @@
}
private void createPowerUi() {
- mPowerUI = new PowerUI(mBroadcastDispatcher);
- mPowerUI.mContext = mContext;
+ mPowerUI = new PowerUI(mContext, mBroadcastDispatcher);
mPowerUI.mComponents = mContext.getComponents();
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
new file mode 100644
index 0000000..4becd52
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.qs.external
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.service.quicksettings.IQSTileService
+import android.service.quicksettings.Tile
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.IWindowManager
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.QSTileHost
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileTest : SysuiTestCase() {
+
+ companion object {
+ const val packageName = "test_package"
+ const val className = "test_class"
+ val componentName = ComponentName(packageName, className)
+ val TILE_SPEC = CustomTile.toSpec(componentName)
+ }
+
+ @Mock private lateinit var mTileHost: QSTileHost
+ @Mock private lateinit var mTileService: IQSTileService
+ @Mock private lateinit var mTileServices: TileServices
+ @Mock private lateinit var mTileServiceManager: TileServiceManager
+ @Mock private lateinit var mWindowService: IWindowManager
+ @Mock private lateinit var mPackageManager: PackageManager
+ @Mock private lateinit var mApplicationInfo: ApplicationInfo
+ @Mock private lateinit var mServiceInfo: ServiceInfo
+
+ private lateinit var customTile: CustomTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mContext.addMockSystemService("window", mWindowService)
+ mContext.setMockPackageManager(mPackageManager)
+ `when`(mTileHost.tileServices).thenReturn(mTileServices)
+ `when`(mTileHost.context).thenReturn(mContext)
+ `when`(mTileServices.getTileWrapper(any(CustomTile::class.java)))
+ .thenReturn(mTileServiceManager)
+ `when`(mTileServiceManager.tileService).thenReturn(mTileService)
+ `when`(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenReturn(mApplicationInfo)
+
+ `when`(mPackageManager.getServiceInfo(any(ComponentName::class.java), anyInt()))
+ .thenReturn(mServiceInfo)
+ mServiceInfo.applicationInfo = mApplicationInfo
+
+ customTile = CustomTile.create(mTileHost, TILE_SPEC)
+ }
+
+ @Test
+ fun testBooleanTileHasBooleanState() {
+ `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+ customTile = CustomTile.create(mTileHost, TILE_SPEC)
+
+ assertTrue(customTile.state is QSTile.BooleanState)
+ assertTrue(customTile.newTileState() is QSTile.BooleanState)
+ }
+
+ @Test
+ fun testRegularTileHasNotBooleanState() {
+ assertFalse(customTile.state is QSTile.BooleanState)
+ assertFalse(customTile.newTileState() is QSTile.BooleanState)
+ }
+
+ @Test
+ fun testValueUpdatedInBooleanTile() {
+ `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+ customTile = CustomTile.create(mTileHost, TILE_SPEC)
+ customTile.qsTile.icon = mock(Icon::class.java)
+ `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java)))
+ .thenReturn(mock(Drawable::class.java))
+
+ val state = customTile.newTileState()
+ assertTrue(state is QSTile.BooleanState)
+
+ customTile.qsTile.state = Tile.STATE_INACTIVE
+ customTile.handleUpdateState(state, null)
+ assertFalse((state as QSTile.BooleanState).value)
+
+ customTile.qsTile.state = Tile.STATE_ACTIVE
+ customTile.handleUpdateState(state, null)
+ assertTrue(state.value)
+
+ customTile.qsTile.state = Tile.STATE_UNAVAILABLE
+ customTile.handleUpdateState(state, null)
+ assertFalse(state.value)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index f35295c..11b0c69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -101,6 +101,7 @@
defaultServiceInfo = new ServiceInfo();
defaultServiceInfo.metaData = new Bundle();
defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true);
+ defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_BOOLEAN_TILE, true);
}
when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt()))
.thenReturn(defaultServiceInfo);
@@ -237,4 +238,9 @@
verifyBind(2);
verify(mMockTileService, times(2)).onStartListening();
}
+
+ @Test
+ public void testBooleanTile() throws Exception {
+ assertTrue(mStateManager.isBooleanTile());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index a0a410d..e67aa69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -52,7 +52,7 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NonPhoneDependencyTest extends SysuiTestCase {
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationListContainer mListContainer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 0569c55..85a0fbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.google.android.collect.Lists;
@@ -83,6 +84,7 @@
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mDependency.injectTestDependency(DeviceProvisionedController.class,
mDeviceProvisionedController);
+ mDependency.injectMockDependency(KeyguardStateController.class);
mContext.addMockSystemService(UserManager.class, mUserManager);
mCurrentUserId = ActivityManager.getCurrentUser();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index de77af8..cb4096c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -42,6 +42,8 @@
import androidx.test.InstrumentationRegistry;
+import com.android.systemui.TestableDependency;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -51,6 +53,7 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.tests.R;
/**
@@ -75,8 +78,11 @@
private ExpandableNotificationRow mRow;
private HeadsUpManagerPhone mHeadsUpManager;
- public NotificationTestHelper(Context context) {
+ public NotificationTestHelper(Context context, TestableDependency dependency) {
mContext = context;
+ dependency.injectMockDependency(NotificationMediaManager.class);
+ dependency.injectMockDependency(BubbleController.class);
+ dependency.injectMockDependency(StatusBarWindowController.class);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
StatusBarStateController stateController = mock(StatusBarStateController.class);
mGroupManager = new NotificationGroupManager(stateController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 9e72504..6efa57d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -103,7 +103,7 @@
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
mDependency.injectTestDependency(ShadeController.class, mShadeController);
- mHelper = new NotificationTestHelper(mContext);
+ mHelper = new NotificationTestHelper(mContext, mDependency);
when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index db2c878..4103ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -47,7 +47,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mNotificationTestHelper = new NotificationTestHelper(getContext());
+ mNotificationTestHelper = new NotificationTestHelper(getContext(), mDependency);
mHostLayout = new FrameLayout(getContext());
mObserver = new AboveShelfObserver(mHostLayout);
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index edd0a10..45e1721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -94,10 +95,11 @@
mDependency.injectTestDependency(NotificationGroupManager.class,
new NotificationGroupManager(mock(StatusBarStateController.class)));
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(NotificationData.KeyguardEnvironment.class, mEnvironment);
when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
- mRow = new NotificationTestHelper(getContext()).createRow();
+ mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
mNotificationFilter = new NotificationFilter();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 8207a04..170c661 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotifLog;
@@ -85,6 +86,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 59c76a8..1be6f61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -74,6 +74,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
@@ -137,13 +138,14 @@
mDependency.injectTestDependency(NotificationGroupManager.class,
new NotificationGroupManager(mock(StatusBarStateController.class)));
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
mNotificationData = new TestableNotificationData(
mock(NotificationSectionsFeatureManager.class));
mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class), "");
- mRow = new NotificationTestHelper(getContext()).createRow();
+ mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
Dependency.get(InitController.class).executePostInitTasks();
}
@@ -159,10 +161,11 @@
@Test
public void testAllRelevantNotisTaggedWithAppOps() throws Exception {
mNotificationData.add(mRow.getEntry());
- ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
mNotificationData.add(row2.getEntry());
ExpandableNotificationRow diffPkg =
- new NotificationTestHelper(getContext()).createRow("pkg", 4000,
+ new NotificationTestHelper(getContext(), mDependency).createRow("pkg", 4000,
Process.myUserHandle());
mNotificationData.add(diffPkg.getEntry());
@@ -189,7 +192,8 @@
@Test
public void testAppOpsRemoval() throws Exception {
mNotificationData.add(mRow.getEntry());
- ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
mNotificationData.add(row2.getEntry());
ArraySet<Integer> expectedOps = new ArraySet<>();
@@ -221,7 +225,8 @@
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications()
throws Exception {
mNotificationData.add(mRow.getEntry());
- ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
mNotificationData.add(row2.getEntry());
when(mEnvironment.isNotificationForCurrentProfiles(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index c56a168..5e6c963 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -82,7 +82,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
mGroupRow = mNotificationTestHelper.createGroup();
mGroupRow.setHeadsUpAnimatingAwayListener(
animatingAway -> mHeadsUpAnimatingAway = animatingAway);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index cc89504..444a6e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -88,7 +88,7 @@
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mDependency.injectMockDependency(BubbleController.class);
- mHelper = new NotificationTestHelper(mContext);
+ mHelper = new NotificationTestHelper(mContext, mDependency);
mBlockingHelperManager = new NotificationBlockingHelperManager(mContext);
// By default, have the shade visible/expanded.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index ccadcc3..71c2e11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -77,7 +77,7 @@
.setContentTitle("Title")
.setContentText("Text")
.setStyle(new Notification.BigTextStyle().bigText("big text"));
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
mBuilder.build());
mRow = spy(row);
mNotificationInflater = new NotificationContentInflater(mRow);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index db6b613..41fe173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -62,6 +62,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -118,9 +119,10 @@
mDeviceProvisionedController);
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mHandler = Handler.createAsync(mTestableLooper.getLooper());
mContext.putComponent(StatusBar.class, mStatusBar);
- mHelper = new NotificationTestHelper(mContext);
+ mHelper = new NotificationTestHelper(mContext, mDependency);
mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager);
mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index 49a6410..d280f18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -44,7 +44,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mRow = new NotificationTestHelper(mContext).createRow();
+ mRow = new NotificationTestHelper(mContext, mDependency).createRow();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 4f844f0..4f45f68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -94,7 +94,7 @@
mNotif = builder.build();
assertTrue(mNotif.hasMediaSession());
- mRow = new NotificationTestHelper(mContext).createRow(mNotif);
+ mRow = new NotificationTestHelper(mContext, mDependency).createRow(mNotif);
RemoteViews views = new RemoteViews(mContext.getPackageName(),
com.android.internal.R.layout.notification_template_material_big_media);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index 5463159..14e2fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -50,7 +50,7 @@
public void setup() throws Exception {
Assert.sMainLooper = TestableLooper.get(this).getLooper();
mView = mock(View.class);
- mRow = new NotificationTestHelper(getContext()).createRow();
+ mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 22d2585..ddd2884e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -45,7 +45,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
mGroup = mNotificationTestHelper.createGroup();
mChildrenContainer = mGroup.getChildrenContainer();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 3f467ea..34a309f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -70,7 +70,7 @@
mBypassController,
new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
+ NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
mFirst = testHelper.createRow();
mFirst.setHeadsUpAnimatingAwayListener(animatingAway
-> mRoundnessManager.onHeadsupAnimatingAwayChanged(mFirst, animatingAway));
@@ -150,7 +150,8 @@
createSection(mFirst, mSecond),
createSection(null, null)
});
- ExpandableNotificationRow row = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
NotificationEntry entry = mock(NotificationEntry.class);
when(entry.getRow()).thenReturn(row);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 11ae0cc..4b82f59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -56,6 +56,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
@@ -70,6 +71,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -167,6 +169,8 @@
mHeadsUpManager,
mKeyguardBypassController,
new FalsingManagerFake(),
+ mock(NotificationLockscreenUserManager.class),
+ mock(NotificationGutsManager.class),
new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext));
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelf(notificationShelf);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
index f614354..16f02d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.IWindowManager;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -38,6 +39,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import org.junit.After;
import org.junit.Before;
@@ -58,7 +60,8 @@
public void setUp() {
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
mAutoHideController =
- spy(new AutoHideController(mContext, Dependency.get(Dependency.MAIN_HANDLER)));
+ spy(new AutoHideController(mContext, Dependency.get(Dependency.MAIN_HANDLER),
+ mock(NotificationRemoteInputManager.class), mock(IWindowManager.class)));
mAutoHideController.mDisplayId = DEFAULT_DISPLAY;
mAutoHideController.mSystemUiVisibility = View.VISIBLE;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index ff9aae7..72bea56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -76,6 +76,8 @@
private Handler mHandler;
@Mock
private KeyguardBypassController mKeyguardBypassController;
+ @Mock
+ private DozeParameters mDozeParameters;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -92,7 +94,8 @@
mStatusBarWindowController);
mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
- mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController);
+ mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController,
+ mDozeParameters);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
new file mode 100644
index 0000000..df1233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DoubleTapHelperTest extends SysuiTestCase {
+
+ private DoubleTapHelper mDoubleTapHelper;
+ private int mTouchSlop;
+ private int mDoubleTouchSlop;
+ @Mock private View mView;
+ @Mock private DoubleTapHelper.ActivationListener mActivationListener;
+ @Mock private DoubleTapHelper.DoubleTapListener mDoubleTapListener;
+ @Mock private DoubleTapHelper.SlideBackListener mSlideBackListener;
+ @Mock private DoubleTapHelper.DoubleTapLogListener mDoubleTapLogListener;
+ @Mock private Resources mResources;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ // The double tap slop has to be less than the regular slop, otherwise it has no effect.
+ mDoubleTouchSlop = mTouchSlop - 1;
+ when(mView.getContext()).thenReturn(mContext);
+ when(mView.getResources()).thenReturn(mResources);
+ when(mResources.getDimension(R.dimen.double_tap_slop))
+ .thenReturn((float) mDoubleTouchSlop);
+
+ mDoubleTapHelper = new DoubleTapHelper(mView,
+ mActivationListener,
+ mDoubleTapListener,
+ mSlideBackListener, mDoubleTapLogListener);
+ }
+
+ @Test
+ public void testDoubleTap_success() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+ verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ verify(mDoubleTapListener).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+
+ @Test
+ public void testSingleTap_timeout() {
+ long downtimeA = SystemClock.uptimeMillis();
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(runnableCaptor.capture(), anyLong());
+ runnableCaptor.getValue().run();
+ verify(mActivationListener).onActiveChanged(true);
+
+ evDownA.recycle();
+ evUpA.recycle();
+ }
+
+ @Test
+ public void testSingleTap_slop() {
+ long downtimeA = SystemClock.uptimeMillis();
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1 + mTouchSlop,
+ 1,
+ 0);
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener, never()).onActiveChanged(true);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ }
+
+ @Test
+ public void testDoubleTap_slop() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1 + mDoubleTouchSlop,
+ 0);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mDoubleTapLogListener).onDoubleTapLog(false, 0, mDoubleTouchSlop);
+ verify(mActivationListener).onActiveChanged(false);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+
+ @Test
+ public void testSlideBack() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ when(mSlideBackListener.onSlideBack()).thenReturn(true);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener, never()).onActiveChanged(true);
+ verify(mActivationListener, never()).onActiveChanged(false);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mActivationListener, never()).onActiveChanged(true);
+ verify(mActivationListener, never()).onActiveChanged(false);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+
+
+ @Test
+ public void testMoreThanTwoTaps() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+ long downtimeC = downtimeB + 100;
+ long downtimeD = downtimeC + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownC = MotionEvent.obtain(downtimeC,
+ downtimeC,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpC = MotionEvent.obtain(downtimeC,
+ downtimeC + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownD = MotionEvent.obtain(downtimeD,
+ downtimeD,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpD = MotionEvent.obtain(downtimeD,
+ downtimeD + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+ verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ verify(mDoubleTapListener).onDoubleTap();
+
+ reset(mView);
+ reset(mActivationListener);
+ reset(mDoubleTapLogListener);
+ reset(mDoubleTapListener);
+
+ mDoubleTapHelper.onTouchEvent(evDownC);
+ mDoubleTapHelper.onTouchEvent(evUpC);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+ verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ mDoubleTapHelper.onTouchEvent(evDownD);
+ mDoubleTapHelper.onTouchEvent(evUpD);
+ verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ verify(mDoubleTapListener).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 60050b1..debc840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -17,68 +17,73 @@
package com.android.systemui.statusbar.phone;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.tuner.TunerService;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DozeParametersTest extends SysuiTestCase {
+ private DozeParameters mDozeParameters;
+
+ @Mock Resources mResources;
+ @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+ @Mock private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
+ @Mock private PowerManager mPowerManager;
+ @Mock private TunerService mTunerService;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mDozeParameters = new DozeParameters(
+ mResources,
+ mAmbientDisplayConfiguration,
+ mAlwaysOnDisplayPolicy,
+ mPowerManager,
+ mTunerService
+ );
+ }
@Test
public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_false() {
- TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
- PowerManager mockedPowerManager = dozeParameters.getPowerManager();
- dozeParameters.setControlScreenOffAnimation(true);
- reset(mockedPowerManager);
- dozeParameters.setControlScreenOffAnimation(false);
- verify(mockedPowerManager).setDozeAfterScreenOff(eq(true));
+ mDozeParameters.setControlScreenOffAnimation(true);
+ reset(mPowerManager);
+ mDozeParameters.setControlScreenOffAnimation(false);
+ verify(mPowerManager).setDozeAfterScreenOff(eq(true));
}
@Test
public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_true() {
- TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
- PowerManager mockedPowerManager = dozeParameters.getPowerManager();
- dozeParameters.setControlScreenOffAnimation(false);
- reset(mockedPowerManager);
- dozeParameters.setControlScreenOffAnimation(true);
- verify(dozeParameters.getPowerManager()).setDozeAfterScreenOff(eq(false));
+ mDozeParameters.setControlScreenOffAnimation(false);
+ reset(mPowerManager);
+ mDozeParameters.setControlScreenOffAnimation(true);
+ verify(mPowerManager).setDozeAfterScreenOff(eq(false));
}
@Test
public void test_getWallpaperAodDuration_when_shouldControlScreenOff() {
- TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
- dozeParameters.setControlScreenOffAnimation(true);
- Assert.assertEquals("wallpaper hides faster when controlling screen off",
- dozeParameters.getWallpaperAodDuration(),
+ mDozeParameters.setControlScreenOffAnimation(true);
+ Assert.assertEquals(
+ "wallpaper hides faster when controlling screen off",
+ mDozeParameters.getWallpaperAodDuration(),
DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY);
}
-
- private class TestableDozeParameters extends DozeParameters {
- private PowerManager mPowerManager;
-
- TestableDozeParameters(Context context) {
- super(context);
- mPowerManager = mock(PowerManager.class);
- }
-
- @Override
- protected PowerManager getPowerManager() {
- return mPowerManager;
- }
- }
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index a38094d..0216d2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Assert;
import org.junit.Before;
@@ -61,11 +62,12 @@
private StatusBarStateController mStatusbarStateController;
private KeyguardBypassController mBypassController;
private NotificationWakeUpCoordinator mWakeUpCoordinator;
+ private KeyguardStateController mKeyguardStateController;
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
+ NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
mFirst = testHelper.createRow();
mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher);
mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
@@ -75,12 +77,14 @@
mStatusbarStateController = mock(StatusBarStateController.class);
mBypassController = mock(KeyguardBypassController.class);
mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
+ mKeyguardStateController = mock(KeyguardStateController.class);
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
mBypassController,
mWakeUpCoordinator,
+ mKeyguardStateController,
mHeadsUpStatusBarView,
mStackScroller,
mPanelView,
@@ -158,6 +162,7 @@
mStatusbarStateController,
mBypassController,
mWakeUpCoordinator,
+ mKeyguardStateController,
mHeadsUpStatusBarView,
mStackScroller,
mPanelView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index ef9665a..6fcf550 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
@@ -36,6 +37,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import org.junit.Before;
import org.junit.Rule;
@@ -85,6 +87,9 @@
when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
.thenReturn(TEST_AUTO_DISMISS_TIME);
when(mVSManager.isReorderingAllowed()).thenReturn(true);
+ mDependency.injectMockDependency(BubbleController.class);
+ mDependency.injectMockDependency(StatusBarWindowController.class);
+ mDependency.injectMockDependency(ConfigurationController.class);
mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mStatusBarWindowView,
mGroupManager, mBar, mVSManager, mStatusBarStateController, mBypassController);
super.setUp();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 688a6fb..2b091f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -1,14 +1,11 @@
package com.android.systemui.statusbar.phone
-import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
-
+import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.KeyguardIndicationController
-
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -17,7 +14,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class KeyguardBottomAreaTest : SysuiTestCase() {
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index cb87d7d..67b8e07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -100,6 +100,7 @@
MockitoAnnotations.initMocks(this);
mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
+ mDependency.injectMockDependency(KeyguardStateController.class);
when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
.thenReturn(KeyguardSecurityModel.SecurityMode.None);
DejankUtils.setImmediate(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 93fdce1..b1580ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -28,8 +28,10 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.LightBarTransitionsController.DarkIntensityApplier;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +52,8 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+ mDependency.injectMockDependency(KeyguardStateController.class);
+ mDependency.injectMockDependency(StatusBarStateController.class);
mLightBarTransitionsController = new LightBarTransitionsController(mContext, mApplier);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
index 81e8abf..098a69f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
@@ -35,7 +35,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.After;
import org.junit.Before;
@@ -63,6 +66,9 @@
(SysuiTestableContext) mContext.createDisplayContext(display);
context.putComponent(CommandQueue.class, mock(CommandQueue.class));
+ mDependency.injectMockDependency(AssistManager.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(KeyguardStateController.class);
mNavBar = new NavigationBarView(context, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
index be69f5f..6433376 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
@@ -33,6 +33,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
import org.junit.Before;
@@ -60,6 +61,8 @@
@Before
public void setup() {
+ mDependency.injectMockDependency(AssistManager.class);
+
mGroup = new ContextualButtonGroup(GROUP_ID);
mBtn0 = new ContextualButton(BUTTON_0_ID, ICON_RES_ID);
mBtn1 = new ContextualButton(BUTTON_1_ID, ICON_RES_ID);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
index cec7feb..991e495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
@@ -31,6 +31,8 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.After;
@@ -51,6 +53,9 @@
@Before
public void setUp() {
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+ mDependency.injectMockDependency(AssistManager.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(NavigationModeController.class);
mNavBarInflaterView = spy(new NavigationBarInflaterView(mContext, null));
doNothing().when(mNavBarInflaterView).createInflaters();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
index bb109bd..1e9378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
@@ -28,7 +28,11 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -44,6 +48,12 @@
@Before
public void setup() {
mDependency.injectMockDependency(IWindowManager.class);
+ mDependency.injectMockDependency(AssistManager.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(NavigationModeController.class);
+ mDependency.injectMockDependency(StatusBarStateController.class);
+ mDependency.injectMockDependency(KeyguardStateController.class);
+
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
NavigationBarView navBar = spy(new NavigationBarView(mContext, null));
when(navBar.getCurrentView()).thenReturn(navBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index a0d264d..2254234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -33,6 +33,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -71,6 +72,7 @@
@Before
public void setup() {
+ mDependency.injectMockDependency(BubbleController.class);
mHeadsUpManager = new HeadsUpManager(mContext) {};
when(mNotificationEntryManager.getPendingNotificationsIterator())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
index dd274c7..493b74d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
@@ -30,6 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -57,6 +58,7 @@
@Before
public void setup() {
+ mDependency.injectMockDependency(BubbleController.class);
initializeGroupManager();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 95e9e67..cff6635 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -60,6 +60,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.InjectionInflationController;
@@ -115,6 +116,7 @@
private FalsingManager mFalsingManager;
@Mock
private KeyguardBypassController mKeyguardBypassController;
+ @Mock private DozeParameters mDozeParameters;
private NotificationPanelView mNotificationPanelView;
@Before
@@ -129,10 +131,11 @@
mDependency.injectMockDependency(ConfigurationController.class);
mDependency.injectMockDependency(ZenModeController.class);
NotificationWakeUpCoordinator coordinator =
- new NotificationWakeUpCoordinator(mContext,
+ new NotificationWakeUpCoordinator(
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(),
- mKeyguardBypassController);
+ mKeyguardBypassController,
+ mDozeParameters);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
coordinator,
@@ -141,7 +144,7 @@
mStatusBarStateController,
new FalsingManagerFake());
mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
- mKeyguardBypassController);
+ mKeyguardBypassController, mStatusBarStateController);
mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
mNotificationPanelView.setBar(mPanelBar);
@@ -218,7 +221,8 @@
private class TestableNotificationPanelView extends NotificationPanelView {
TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler expansionHandler,
- KeyguardBypassController bypassController) {
+ KeyguardBypassController bypassController,
+ SysuiStatusBarStateController statusBarStateController) {
super(
NotificationPanelViewTest.this.mContext,
null,
@@ -235,7 +239,10 @@
new NotificationEntryManager(new NotificationData(mock(
NotificationSectionsFeatureManager.class), mock(NotifLog.class)),
mock(NotifLog.class)),
- mock(DozeLog.class));
+ mock(KeyguardStateController.class),
+ statusBarStateController,
+ mock(DozeLog.class),
+ mDozeParameters);
mNotificationStackScroller = mNotificationStackScrollLayout;
mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 214e26a..f3ff7ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -133,6 +133,20 @@
}
@Test
+ public void transitionToOff() {
+ mScrimController.transitionTo(ScrimState.OFF);
+ mScrimController.finishAnimationsImmediately();
+
+ assertScrimAlpha(OPAQUE /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
+ }
+
+ @Test
public void transitionToAod_withRegularWallpaper() {
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index aafcdd0..3e07cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -92,6 +93,7 @@
MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(StatusBarWindowController.class);
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 266abcf..d8a68b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -140,7 +140,7 @@
when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
// Create standard notification with contentIntent
mNotificationRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 24ec109..210c9d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -35,21 +35,37 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper()
@@ -63,11 +79,33 @@
@Before
public void setup() {
+ NotificationRemoteInputManager notificationRemoteInputManager =
+ mock(NotificationRemoteInputManager.class);
+ when(notificationRemoteInputManager.getController())
+ .thenReturn(mock(RemoteInputController.class));
mMetricsLogger = new FakeMetricsLogger();
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mCommandQueue = new CommandQueue(mContext);
mContext.putComponent(CommandQueue.class, mCommandQueue);
+ mDependency.injectTestDependency(StatusBarStateController.class,
+ mock(SysuiStatusBarStateController.class));
mDependency.injectTestDependency(ShadeController.class, mShadeController);
+ mDependency.injectTestDependency(NotificationRemoteInputManager.class,
+ notificationRemoteInputManager);
+ mDependency.injectMockDependency(NotificationViewHierarchyManager.class);
+ mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
+ mDependency.injectMockDependency(NotificationInterruptionStateProvider.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
+ mDependency.injectMockDependency(VisualStabilityManager.class);
+ mDependency.injectMockDependency(NotificationGutsManager.class);
+ mDependency.injectMockDependency(StatusBarWindowController.class);
+ mDependency.injectMockDependency(InitController.class);
+ NotificationData notificationData = mock(NotificationData.class);
+ when(notificationData.getNotificationsForCurrentUser()).thenReturn(new ArrayList<>());
+ NotificationEntryManager entryManager =
+ mDependency.injectMockDependency(NotificationEntryManager.class);
+ when(entryManager.getNotificationData()).thenReturn(notificationData);
StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class);
when(statusBarWindowView.getResources()).thenReturn(mContext.getResources());
@@ -77,7 +115,7 @@
mock(DozeScrimController.class), mock(ScrimController.class),
mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
mock(NotificationAlertingManager.class),
- mock(NotificationRowBinderImpl.class));
+ mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 4b6ca56..a65f5a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -24,18 +24,19 @@
import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.content.Intent;
-import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -47,14 +48,13 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private UserManager mUserManager;
-
- // Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private ShadeController mShadeController;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock private ActivityStarter mActivityStarter;
private int mCurrentUserId = 0;
private StatusBarRemoteInputCallback mRemoteInputCallback;
@@ -71,7 +71,9 @@
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
- mock(NotificationGroupManager.class)));
+ mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
+ mKeyguardStateController, mStatusBarStateController, mActivityStarter,
+ mShadeController));
mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 12e9be1..8f1b6017 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -220,6 +220,7 @@
@Mock private StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
@Mock private StatusBarWindowViewController mStatusBarWindowViewController;
@Mock private NotifLog mNotifLog;
+ @Mock private DozeParameters mDozeParameters;
@Before
public void setup() throws Exception {
@@ -286,6 +287,7 @@
.thenReturn(mStatusBarWindowViewController);
mStatusBar = new StatusBar(
+ mContext,
mLightBarController,
mAutoHideController,
mKeyguardUpdateMonitor,
@@ -342,10 +344,10 @@
configurationController,
mStatusBarWindowController,
mStatusBarWindowViewControllerBuilder,
- mNotifLog);
+ mNotifLog,
+ mDozeParameters);
// TODO: we should be able to call mStatusBar.start() and have all the below values
// initialized automatically.
- mStatusBar.mContext = mContext;
mStatusBar.mComponents = mContext.getComponents();
mStatusBar.mStatusBarKeyguardViewManager = mStatusBarKeyguardViewManager;
mStatusBar.mStatusBarWindow = mStatusBarWindowView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index 4ffaeae..a21a658 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -32,7 +32,9 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -48,20 +50,15 @@
@SmallTest
public class StatusBarWindowControllerTest extends SysuiTestCase {
- @Mock
- private WindowManager mWindowManager;
- @Mock
- private DozeParameters mDozeParameters;
- @Mock
- private ViewGroup mStatusBarView;
- @Mock
- private IActivityManager mActivityManager;
- @Mock
- private SysuiStatusBarStateController mStatusBarStateController;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private KeyguardBypassController mKeyguardBypassController;
+ @Mock private WindowManager mWindowManager;
+ @Mock private DozeParameters mDozeParameters;
+ @Mock private ViewGroup mStatusBarView;
+ @Mock private IActivityManager mActivityManager;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock private ConfigurationController mConfigurationController;
+ @Mock private KeyguardBypassController mKeyguardBypassController;
+ @Mock private SysuiColorExtractor mColorExtractor;
+ @Mock ColorExtractor.GradientColors mGradientColors;
private StatusBarWindowController mStatusBarWindowController;
@@ -69,10 +66,11 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
+ mConfigurationController, mKeyguardBypassController, mColorExtractor);
mStatusBarWindowController.add(mStatusBarView, 100 /* height */);
}
@@ -96,9 +94,6 @@
@Test
public void testOnThemeChanged_doesntCrash() {
- mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
- mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
mStatusBarWindowController.onThemeChanged();
}
@@ -109,9 +104,6 @@
@Test
public void testSetForcePluginOpen_beforeStatusBarInitialization() {
- mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
- mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
mStatusBarWindowController.setForcePluginOpen(true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
index 9f4dfb4..7c1dfa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
@@ -34,9 +34,11 @@
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
@@ -61,11 +63,14 @@
@Mock private PluginManager mPluginManager;
@Mock private TunerService mTunerService;
@Mock private DragDownHelper mDragDownHelper;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private ShadeController mShadeController;
@Mock private NotificationLockscreenUserManager mNotificationLockScreenUserManager;
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private StatusBar mStatusBar;
@Mock private DozeLog mDozeLog;
+ @Mock private DozeParameters mDozeParameters;
@Before
public void setUp() {
@@ -88,7 +93,10 @@
mTunerService,
mNotificationLockScreenUserManager,
mNotificationEntryManager,
- mDozeLog)
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mDozeLog,
+ mDozeParameters)
.setShadeController(mShadeController)
.setStatusBarWindowView(mView)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
index d16dc16..943674a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
@@ -40,6 +40,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.recents.OverviewProxyService;
import org.junit.Before;
import org.junit.Test;
@@ -68,6 +69,7 @@
MockitoAnnotations.initMocks(this);
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mBubbleController = mDependency.injectMockDependency(BubbleController.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
TestableLooper.get(this).runWithLooper(() -> {
mKeyButtonView = new KeyButtonView(mContext, null, 0, mInputManager);
});
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
new file mode 100644
index 0000000..e57bbc1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardStateControllerTest extends SysuiTestCase {
+
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ private KeyguardStateController mKeyguardStateController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mKeyguardStateController = new KeyguardStateControllerImpl(mContext,
+ mKeyguardUpdateMonitor, mLockPatternUtils);
+ }
+
+ @Test
+ public void testAddCallback_registersListener() {
+ verify(mKeyguardUpdateMonitor).registerCallback(any());
+ }
+
+ @Test
+ public void testIsShowing() {
+ assertThat(mKeyguardStateController.isShowing()).isFalse();
+ mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
+ assertThat(mKeyguardStateController.isShowing()).isTrue();
+ }
+
+ @Test
+ public void testIsMethodSecure() {
+ assertThat(mKeyguardStateController.isMethodSecure()).isFalse();
+
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.isMethodSecure()).isTrue();
+ }
+
+ @Test
+ public void testIsOccluded() {
+ assertThat(mKeyguardStateController.isOccluded()).isFalse();
+ mKeyguardStateController.notifyKeyguardState(false /* showing */, true /* occluded */);
+ assertThat(mKeyguardStateController.isOccluded()).isTrue();
+ }
+
+ @Test
+ public void testCanSkipLockScreen() {
+ // Can skip because LockPatternUtils#isSecure is false
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+
+ // Cannot skip after there's a password/pin/pattern
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+ // Unless user is authenticated
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+ }
+
+ @Test
+ public void testIsUnlocked() {
+ // Is unlocked whenever the keyguard is not showing
+ assertThat(mKeyguardStateController.isShowing()).isFalse();
+ assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+
+ // Unlocked if showing, but insecure
+ mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
+ assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+
+ // Locked if showing, and requires password
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.isUnlocked()).isFalse();
+
+ // But unlocked after #getUserCanSkipBouncer allows it
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+ }
+
+ @Test
+ public void testIsTrusted() {
+ assertThat(mKeyguardStateController.isTrusted()).isFalse();
+
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+
+ assertThat(mKeyguardStateController.isTrusted()).isTrue();
+ }
+
+ @Test
+ public void testCallbacksAreInvoked() {
+ KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
+ mKeyguardStateController.addCallback(callback);
+
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+
+ verify(callback).onUnlockedChanged();
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index bc468bf..390e812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -106,7 +106,8 @@
@Test
public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception {
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
+ .createRow();
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
setTestPendingIntent(view);
@@ -127,7 +128,7 @@
private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)
throws Exception {
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
DUMMY_MESSAGE_APP_PKG,
UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID),
toUser);
@@ -169,7 +170,8 @@
@Test
public void testNoCrashWithoutVisibilityListener() throws Exception {
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
+ .createRow();
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
view.setOnVisibilityChangedListener(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 0d56cbe..b5e4cb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -115,6 +116,7 @@
});
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectMockDependency(NotificationRemoteInputManager.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 5b826b1..b0e401b 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -2584,15 +2584,15 @@
// ACTION: Logged when trampoline activity finishes.
// TIME: Indicates total time taken by trampoline activity to finish in MS.
- PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 523;
+ PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 523 [deprecated=true];
// ACTION: Logged when encryption activity finishes.
// TIME: Indicates total time taken by post encryption activity to finish in MS.
- PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 524;
+ PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 524 [deprecated=true];
// ACTION: Logged when finalization activity finishes.
// TIME: Indicates time taken by finalization activity to finish in MS.
- PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 525;
+ PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 525 [deprecated=true];
// OPEN: Settings Support > Phone/Chat -> Disclaimer
DIALOG_SUPPORT_DISCLAIMER = 526;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 91269c7..e909e7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,13 +16,6 @@
package com.android.server.accessibility;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
-
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -50,8 +43,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IFingerprintService;
@@ -119,7 +110,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -136,6 +126,7 @@
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements AbstractAccessibilityServiceConnection.SystemSupport,
+ AccessibilityUserState.ServiceInfoChangeListener,
AccessibilityWindowManager.AccessibilityEventSender,
AccessibilitySecurityPolicy.AccessibilityUserManager {
@@ -178,12 +169,6 @@
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
- private final Rect mTempRect = new Rect();
-
- private final Rect mTempRect1 = new Rect();
-
- private final Point mTempPoint = new Point();
-
private final PackageManager mPackageManager;
private final PowerManager mPowerManager;
@@ -226,7 +211,7 @@
private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
new RemoteCallbackList<>();
- private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ private final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock);
@@ -237,7 +222,7 @@
private boolean mIsAccessibilityButtonShown;
- private UserState getCurrentUserStateLocked() {
+ private AccessibilityUserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
}
@@ -293,6 +278,11 @@
return mIsAccessibilityButtonShown;
}
+ @Override
+ public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ }
+
@Nullable
public FingerprintGestureDispatcher getFingerprintGestureDispatcher() {
return mFingerprintGestureDispatcher;
@@ -307,40 +297,40 @@
}
}
- private UserState getUserState(int userId) {
+ private AccessibilityUserState getUserState(int userId) {
synchronized (mLock) {
return getUserStateLocked(userId);
}
}
- private UserState getUserStateLocked(int userId) {
- UserState state = mUserStates.get(userId);
+ @NonNull
+ private AccessibilityUserState getUserStateLocked(int userId) {
+ AccessibilityUserState state = mUserStates.get(userId);
if (state == null) {
- state = new UserState(userId);
+ state = new AccessibilityUserState(userId, mContext, this);
mUserStates.put(userId, state);
}
return state;
}
boolean getBindInstantServiceAllowed(int userId) {
- final UserState userState = getUserState(userId);
- if (userState == null) return false;
- return userState.getBindInstantServiceAllowed();
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getUserStateLocked(userId);
+ return userState.getBindInstantServiceAllowedLocked();
+ }
}
void setBindInstantServiceAllowed(int userId, boolean allowed) {
- UserState userState;
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+ "setBindInstantServiceAllowed");
synchronized (mLock) {
- userState = getUserState(userId);
- if (userState == null) {
- if (!allowed) {
- return;
- }
- userState = new UserState(userId);
- mUserStates.put(userId, userState);
+ final AccessibilityUserState userState = getUserStateLocked(userId);
+ if (allowed != userState.getBindInstantServiceAllowedLocked()) {
+ userState.setBindInstantServiceAllowedLocked(allowed);
+ onUserStateChangedLocked(userState);
}
}
- userState.setBindInstantServiceAllowed(allowed);
}
private void registerBroadcastReceivers() {
@@ -354,7 +344,7 @@
return;
}
// We will update when the automation service dies.
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
// We have to reload the installed services since some services may
// have different attributes, resolve info (does not support equals),
// etc. Remove them then to force reload.
@@ -376,8 +366,8 @@
if (userId != mCurrentUserId) {
return;
}
- UserState userState = getUserStateLocked(userId);
- boolean reboundAService = userState.mBindingServices.removeIf(
+ AccessibilityUserState userState = getUserStateLocked(userId);
+ boolean reboundAService = userState.getBindingServicesLocked().removeIf(
component -> component != null
&& component.getPackageName().equals(packageName));
if (reboundAService) {
@@ -395,14 +385,14 @@
if (userId != mCurrentUserId) {
return;
}
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
ComponentName comp = it.next();
String compPkg = comp.getPackageName();
if (compPkg.equals(packageName)) {
it.remove();
- userState.mBindingServices.remove(comp);
+ userState.getBindingServicesLocked().remove(comp);
// Update the enabled services setting.
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
@@ -430,7 +420,7 @@
if (userId != mCurrentUserId) {
return false;
}
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
ComponentName comp = it.next();
@@ -441,7 +431,7 @@
return true;
}
it.remove();
- userState.mBindingServices.remove(comp);
+ userState.getBindingServicesLocked().remove(comp);
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mEnabledServices, userId);
@@ -478,7 +468,7 @@
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
// We will update when the automation service dies.
synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (readConfigurationForUserStateLocked(userState)) {
onUserStateChangedLocked(userState);
}
@@ -509,7 +499,7 @@
// If the client is from a process that runs across users such as
// the system UI or the system we add it to the global state that
// is shared across users.
- UserState userState = getUserStateLocked(resolvedUserId);
+ AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
Client client = new Client(callback, Binder.getCallingUid(), userState);
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
mGlobalClients.register(callback, client);
@@ -517,7 +507,7 @@
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
return IntPair.of(
- userState.getClientState(),
+ getClientStateLocked(userState),
client.mLastSentRelevantEventTypes);
} else {
userState.mUserClients.register(callback, client);
@@ -529,7 +519,7 @@
+ " and userId:" + mCurrentUserId);
}
return IntPair.of(
- (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
+ (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
client.mLastSentRelevantEventTypes);
}
}
@@ -644,7 +634,7 @@
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
// The automation service can suppress other services.
- final UserState userState = getUserStateLocked(resolvedUserId);
+ final AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
return Collections.emptyList();
}
@@ -754,15 +744,15 @@
}
synchronized (mLock) {
// Set the temporary state.
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
- userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
- userState.mIsDisplayMagnificationEnabled = false;
- userState.mIsNavBarMagnificationEnabled = false;
- userState.mIsAutoclickEnabled = false;
+ userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
+ userState.setDisplayMagnificationEnabledLocked(false);
+ userState.setNavBarMagnificationEnabledLocked(false);
+ userState.setAutoclickEnabledLocked(false);
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
- userState.mBindingServices.clear();
+ userState.getBindingServicesLocked().clear();
userState.mTouchExplorationGrantedServices.clear();
userState.mTouchExplorationGrantedServices.add(service);
@@ -943,7 +933,7 @@
}
// Disconnect from services for the old user.
- UserState oldUserState = getCurrentUserStateLocked();
+ AccessibilityUserState oldUserState = getCurrentUserStateLocked();
oldUserState.onSwitchToAnotherUserLocked();
// Disable the local managers for the old user.
@@ -960,7 +950,7 @@
// The user changed.
mCurrentUserId = userId;
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
readConfigurationForUserStateLocked(userState);
// Even if reading did not yield change, we have to update
@@ -979,8 +969,8 @@
private void announceNewUserIfNeeded() {
synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
- if (userState.isHandlingAccessibilityEvents()) {
+ AccessibilityUserState userState = getCurrentUserStateLocked();
+ if (userState.isHandlingAccessibilityEventsLocked()) {
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
@@ -997,7 +987,7 @@
synchronized (mLock) {
int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId);
if (parentUserId == mCurrentUserId) {
- UserState userState = getUserStateLocked(mCurrentUserId);
+ AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
onUserStateChangedLocked(userState);
}
}
@@ -1015,7 +1005,7 @@
readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false);
readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true);
- UserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
+ AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
userState.mEnabledServices.clear();
userState.mEnabledServices.addAll(mTempComponentNameSet);
persistComponentNamesToSettingLocked(
@@ -1025,6 +1015,10 @@
onUserStateChangedLocked(userState);
}
+ private int getClientStateLocked(AccessibilityUserState userState) {
+ return userState.getClientStateLocked(mUiAutomationManager.isUiAutomationRunningLocked());
+ }
+
private InteractionBridge getInteractionBridge() {
synchronized (mLock) {
if (mInteractionBridge == null) {
@@ -1044,7 +1038,7 @@
// gestures to avoid user frustration when different
// behavior is observed from different combinations of
// enabled accessibility services.
- UserState state = getCurrentUserStateLocked();
+ AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
@@ -1056,7 +1050,7 @@
}
private void notifyClearAccessibilityCacheLocked() {
- UserState state = getCurrentUserStateLocked();
+ AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
service.notifyClearAccessibilityNodeInfoCache();
@@ -1065,25 +1059,17 @@
private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
- final UserState state = getCurrentUserStateLocked();
+ final AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
- private void notifySoftKeyboardShowModeChangedLocked(int showMode) {
- final UserState state = getCurrentUserStateLocked();
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- service.notifySoftKeyboardShowModeChangedLocked(showMode);
- }
- }
-
private void notifyAccessibilityButtonClickedLocked(int displayId) {
- final UserState state = getCurrentUserStateLocked();
+ final AccessibilityUserState state = getCurrentUserStateLocked();
- int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
+ int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
@@ -1095,7 +1081,7 @@
return;
}
if (potentialTargets == 1) {
- if (state.mIsNavBarMagnificationEnabled) {
+ if (state.isNavBarMagnificationEnabledLocked()) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
displayId));
@@ -1110,13 +1096,13 @@
}
}
} else {
- if (state.mServiceAssignedToAccessibilityButton == null
- && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if (state.getServiceAssignedToAccessibilityButtonLocked() == null
+ && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
displayId));
- } else if (state.mIsNavBarMagnificationEnabled
- && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ } else if (state.isNavBarMagnificationEnabledLocked()
+ && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
displayId));
@@ -1125,7 +1111,7 @@
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
- state.mServiceAssignedToAccessibilityButton))) {
+ state.getServiceAssignedToAccessibilityButtonLocked()))) {
service.notifyAccessibilityButtonClickedLocked(displayId);
return;
}
@@ -1154,7 +1140,7 @@
}
private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
- final UserState state = getCurrentUserStateLocked();
+ final AccessibilityUserState state = getCurrentUserStateLocked();
mIsAccessibilityButtonShown = available;
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection clientConnection = state.mBoundServices.get(i);
@@ -1165,7 +1151,7 @@
}
}
- private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
+ private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
mTempAccessibilityServiceInfoList.clear();
int flags = PackageManager.GET_SERVICES
@@ -1174,7 +1160,7 @@
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- if (userState.getBindInstantServiceAllowed()) {
+ if (userState.getBindInstantServiceAllowedLocked()) {
flags |= PackageManager.MATCH_INSTANT;
}
@@ -1209,7 +1195,7 @@
return false;
}
- private boolean readInstalledAccessibilityShortcutLocked(UserState userState) {
+ private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
.getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
mContext, mCurrentUserId);
@@ -1221,7 +1207,7 @@
return false;
}
- private boolean readEnabledAccessibilityServicesLocked(UserState userState) {
+ private boolean readEnabledAccessibilityServicesLocked(AccessibilityUserState userState) {
mTempComponentNameSet.clear();
readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mUserId, mTempComponentNameSet);
@@ -1236,7 +1222,7 @@
}
private boolean readTouchExplorationGrantedAccessibilityServicesLocked(
- UserState userState) {
+ AccessibilityUserState userState) {
mTempComponentNameSet.clear();
readComponentNamesFromSettingLocked(
Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
@@ -1261,7 +1247,7 @@
private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
boolean isDefault) {
try {
- UserState state = getCurrentUserStateLocked();
+ AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
@@ -1276,7 +1262,7 @@
}
}
- private void updateRelevantEventsLocked(UserState userState) {
+ private void updateRelevantEventsLocked(AccessibilityUserState userState) {
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
int relevantEventTypes;
@@ -1296,7 +1282,7 @@
});
}
- private int computeRelevantEventTypesLocked(UserState userState, Client client) {
+ private int computeRelevantEventTypesLocked(AccessibilityUserState userState, Client client) {
int relevantEventTypes = 0;
int serviceCount = userState.mBoundServices.size();
@@ -1342,20 +1328,11 @@
}
private void broadcastToClients(
- UserState userState, Consumer<Client> clientAction) {
+ AccessibilityUserState userState, Consumer<Client> clientAction) {
mGlobalClients.broadcastForEachCookie(clientAction);
userState.mUserClients.broadcastForEachCookie(clientAction);
}
- private void unbindAllServicesLocked(UserState userState) {
- List<AccessibilityServiceConnection> services = userState.mBoundServices;
- for (int count = services.size(); count > 0; count--) {
- // When the service is unbound, it disappears from the list, so there's no need to
- // keep track of the index
- services.get(0).unbindLocked();
- }
- }
-
/**
* Populates a set with the {@link ComponentName}s stored in a colon
* separated value setting for a given user.
@@ -1422,7 +1399,7 @@
}
}
- private void updateServicesLocked(UserState userState) {
+ private void updateServicesLocked(AccessibilityUserState userState) {
Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
@@ -1442,7 +1419,7 @@
}
// Wait for the binding if it is in process.
- if (userState.mBindingServices.contains(componentName)) {
+ if (userState.getBindingServicesLocked().contains(componentName)) {
continue;
}
if (userState.mEnabledServices.contains(componentName)
@@ -1478,15 +1455,15 @@
if (audioManager != null) {
audioManager.setAccessibilityServiceUids(mTempIntArray);
}
- updateAccessibilityEnabledSetting(userState);
+ updateAccessibilityEnabledSettingLocked(userState);
}
- private void scheduleUpdateClientsIfNeededLocked(UserState userState) {
- final int clientState = userState.getClientState();
- if (userState.mLastSentClientState != clientState
+ private void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+ final int clientState = getClientStateLocked(userState);
+ if (userState.getLastSentClientStateLocked() != clientState
&& (mGlobalClients.getRegisteredCallbackCount() > 0
|| userState.mUserClients.getRegisteredCallbackCount() > 0)) {
- userState.mLastSentClientState = clientState;
+ userState.setLastSentClientStateLocked(clientState);
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendStateToAllClients,
this, clientState, userState.mUserId));
@@ -1508,7 +1485,8 @@
client -> client.setState(clientState)));
}
- private void scheduleNotifyClientsOfServicesStateChangeLocked(UserState userState) {
+ private void scheduleNotifyClientsOfServicesStateChangeLocked(
+ AccessibilityUserState userState) {
updateRecommendedUiTimeoutLocked(userState);
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendServicesStateChanged,
@@ -1527,44 +1505,45 @@
client -> client.notifyServicesStateChanged(uiTimeout)));
}
- private void scheduleUpdateInputFilter(UserState userState) {
+ private void scheduleUpdateInputFilter(AccessibilityUserState userState) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::updateInputFilter, this, userState));
}
- private void scheduleUpdateFingerprintGestureHandling(UserState userState) {
+ private void scheduleUpdateFingerprintGestureHandling(AccessibilityUserState userState) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::updateFingerprintGestureHandling,
this, userState));
}
- private void updateInputFilter(UserState userState) {
+ private void updateInputFilter(AccessibilityUserState userState) {
if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) return;
boolean setInputFilter = false;
AccessibilityInputFilter inputFilter = null;
synchronized (mLock) {
int flags = 0;
- if (userState.mIsDisplayMagnificationEnabled) {
+ if (userState.isDisplayMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
}
- if (userState.mIsNavBarMagnificationEnabled) {
+ if (userState.isNavBarMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
}
if (userHasMagnificationServicesLocked(userState)) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
}
// Touch exploration without accessibility makes no sense.
- if (userState.isHandlingAccessibilityEvents() && userState.mIsTouchExplorationEnabled) {
+ if (userState.isHandlingAccessibilityEventsLocked()
+ && userState.isTouchExplorationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
}
- if (userState.mIsFilterKeyEventsEnabled) {
+ if (userState.isFilterKeyEventsEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
}
- if (userState.mIsAutoclickEnabled) {
+ if (userState.isAutoclickEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
}
- if (userState.mIsPerformGesturesEnabled) {
+ if (userState.isPerformGesturesEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
}
if (flags != 0) {
@@ -1597,8 +1576,8 @@
String label = service.getServiceInfo().getResolveInfo()
.loadLabel(mContext.getPackageManager()).toString();
- final UserState userState = getCurrentUserStateLocked();
- if (userState.mIsTouchExplorationEnabled) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ if (userState.isTouchExplorationEnabledLocked()) {
return;
}
if (mEnableTouchExplorationDialog != null
@@ -1608,40 +1587,40 @@
mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // The user allowed the service to toggle touch exploration.
- userState.mTouchExplorationGrantedServices.add(service.mComponentName);
- persistComponentNamesToSettingLocked(
- Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- userState.mTouchExplorationGrantedServices, userState.mUserId);
- // Enable touch exploration.
- userState.mIsTouchExplorationEnabled = true;
- final long identity = Binder.clearCallingIdentity();
- try {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
- userState.mUserId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- onUserStateChangedLocked(userState);
- }
- })
- .setNegativeButton(android.R.string.cancel, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
- .setTitle(R.string.enable_explore_by_touch_warning_title)
- .setMessage(mContext.getString(
- R.string.enable_explore_by_touch_warning_message, label))
- .create();
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // The user allowed the service to toggle touch exploration.
+ userState.mTouchExplorationGrantedServices.add(service.mComponentName);
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices, userState.mUserId);
+ // Enable touch exploration.
+ userState.setTouchExplorationEnabledLocked(true);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
+ userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ onUserStateChangedLocked(userState);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .setTitle(R.string.enable_explore_by_touch_warning_title)
+ .setMessage(mContext.getString(
+ R.string.enable_explore_by_touch_warning_message, label))
+ .create();
mEnableTouchExplorationDialog.getWindow().setType(
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
mEnableTouchExplorationDialog.getWindow().getAttributes().privateFlags
- |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true);
mEnableTouchExplorationDialog.show();
}
@@ -1652,7 +1631,7 @@
*
* @param userState the new user state
*/
- private void onUserStateChangedLocked(UserState userState) {
+ private void onUserStateChangedLocked(AccessibilityUserState userState) {
// TODO: Remove this hack
mInitialized = true;
updateLegacyCapabilitiesLocked(userState);
@@ -1670,7 +1649,7 @@
updateAccessibilityButtonTargetsLocked(userState);
}
- private void updateWindowsForAccessibilityCallbackLocked(UserState userState) {
+ private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) {
// We observe windows for accessibility only if there is at least
// one bound service that can retrieve window content that specified
// it is interested in accessing such windows. For services that are
@@ -1702,7 +1681,7 @@
}
}
- private void updateLegacyCapabilitiesLocked(UserState userState) {
+ private void updateLegacyCapabilitiesLocked(AccessibilityUserState userState) {
// Up to JB-MR1 we had a white list with services that can enable touch
// exploration. When a service is first started we show a dialog to the
// use to get a permission to white list the service.
@@ -1724,20 +1703,20 @@
}
}
- private void updatePerformGesturesLocked(UserState userState) {
+ private void updatePerformGesturesLocked(AccessibilityUserState userState) {
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if ((service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0) {
- userState.mIsPerformGesturesEnabled = true;
+ userState.setPerformGesturesEnabledLocked(true);
return;
}
}
- userState.mIsPerformGesturesEnabled = false;
+ userState.setPerformGesturesEnabledLocked(false);
}
- private void updateFilterKeyEventsLocked(UserState userState) {
+ private void updateFilterKeyEventsLocked(AccessibilityUserState userState) {
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
AccessibilityServiceConnection service = userState.mBoundServices.get(i);
@@ -1745,14 +1724,14 @@
&& (service.getCapabilities()
& AccessibilityServiceInfo
.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) {
- userState.mIsFilterKeyEventsEnabled = true;
+ userState.setFilterKeyEventsEnabledLocked(true);
return;
}
}
- userState.mIsFilterKeyEventsEnabled = false;
+ userState.setFilterKeyEventsEnabledLocked(false);
}
- private boolean readConfigurationForUserStateLocked(UserState userState) {
+ private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
@@ -1767,10 +1746,10 @@
return somethingChanged;
}
- private void updateAccessibilityEnabledSetting(UserState userState) {
+ private void updateAccessibilityEnabledSettingLocked(AccessibilityUserState userState) {
final long identity = Binder.clearCallingIdentity();
final boolean isA11yEnabled = mUiAutomationManager.isUiAutomationRunningLocked()
- || userState.isHandlingAccessibilityEvents();
+ || userState.isHandlingAccessibilityEventsLocked();
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED,
@@ -1781,18 +1760,18 @@
}
}
- private boolean readTouchExplorationEnabledSettingLocked(UserState userState) {
+ private boolean readTouchExplorationEnabledSettingLocked(AccessibilityUserState userState) {
final boolean touchExplorationEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1;
- if (touchExplorationEnabled != userState.mIsTouchExplorationEnabled) {
- userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
+ if (touchExplorationEnabled != userState.isTouchExplorationEnabledLocked()) {
+ userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
return true;
}
return false;
}
- private boolean readMagnificationEnabledSettingsLocked(UserState userState) {
+ private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) {
final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
@@ -1801,40 +1780,40 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
0, userState.mUserId) == 1;
- if ((displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled)
- || (navBarMagnificationEnabled != userState.mIsNavBarMagnificationEnabled)) {
- userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled;
- userState.mIsNavBarMagnificationEnabled = navBarMagnificationEnabled;
+ if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
+ || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+ userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
+ userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
return true;
}
return false;
}
- private boolean readAutoclickEnabledSettingLocked(UserState userState) {
+ private boolean readAutoclickEnabledSettingLocked(AccessibilityUserState userState) {
final boolean autoclickEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
0, userState.mUserId) == 1;
- if (autoclickEnabled != userState.mIsAutoclickEnabled) {
- userState.mIsAutoclickEnabled = autoclickEnabled;
+ if (autoclickEnabled != userState.isAutoclickEnabledLocked()) {
+ userState.setAutoclickEnabledLocked(autoclickEnabled);
return true;
}
return false;
}
- private boolean readHighTextContrastEnabledSettingLocked(UserState userState) {
+ private boolean readHighTextContrastEnabledSettingLocked(AccessibilityUserState userState) {
final boolean highTextContrastEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0,
userState.mUserId) == 1;
- if (highTextContrastEnabled != userState.mIsTextHighContrastEnabled) {
- userState.mIsTextHighContrastEnabled = highTextContrastEnabled;
+ if (highTextContrastEnabled != userState.isTextHighContrastEnabledLocked()) {
+ userState.setTextHighContrastEnabledLocked(highTextContrastEnabled);
return true;
}
return false;
}
- private void updateTouchExplorationLocked(UserState userState) {
+ private void updateTouchExplorationLocked(AccessibilityUserState userState) {
boolean enabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
@@ -1844,8 +1823,8 @@
break;
}
}
- if (enabled != userState.mIsTouchExplorationEnabled) {
- userState.mIsTouchExplorationEnabled = enabled;
+ if (enabled != userState.isTouchExplorationEnabledLocked()) {
+ userState.setTouchExplorationEnabledLocked(enabled);
final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
@@ -1857,60 +1836,61 @@
}
}
- private boolean readAccessibilityShortcutSettingLocked(UserState userState) {
+ private boolean readAccessibilityShortcutSettingLocked(AccessibilityUserState userState) {
String componentNameToEnableString = AccessibilityShortcutController
.getTargetServiceComponentNameString(mContext, userState.mUserId);
if ((componentNameToEnableString == null) || componentNameToEnableString.isEmpty()) {
- if (userState.mServiceToEnableWithShortcut == null) {
+ if (userState.getServiceToEnableWithShortcutLocked() == null) {
return false;
}
- userState.mServiceToEnableWithShortcut = null;
+ userState.setServiceToEnableWithShortcutLocked(null);
return true;
}
ComponentName componentNameToEnable =
ComponentName.unflattenFromString(componentNameToEnableString);
if ((componentNameToEnable != null)
- && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
+ && componentNameToEnable.equals(userState.getServiceToEnableWithShortcutLocked())) {
return false;
}
- userState.mServiceToEnableWithShortcut = componentNameToEnable;
+ userState.setServiceToEnableWithShortcutLocked(componentNameToEnable);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
return true;
}
- private boolean readAccessibilityButtonSettingsLocked(UserState userState) {
+ private boolean readAccessibilityButtonSettingsLocked(AccessibilityUserState userState) {
String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
if (TextUtils.isEmpty(componentId)) {
- if ((userState.mServiceAssignedToAccessibilityButton == null)
- && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if ((userState.getServiceAssignedToAccessibilityButtonLocked() == null)
+ && !userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
return false;
}
- userState.mServiceAssignedToAccessibilityButton = null;
- userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ userState.setServiceAssignedToAccessibilityButtonLocked(null);
+ userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
return true;
}
if (componentId.equals(MagnificationController.class.getName())) {
- if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if (userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
return false;
}
- userState.mServiceAssignedToAccessibilityButton = null;
- userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true;
+ userState.setServiceAssignedToAccessibilityButtonLocked(null);
+ userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
return true;
}
ComponentName componentName = ComponentName.unflattenFromString(componentId);
- if (Objects.equals(componentName, userState.mServiceAssignedToAccessibilityButton)) {
+ if (Objects.equals(componentName,
+ userState.getServiceAssignedToAccessibilityButtonLocked())) {
return false;
}
- userState.mServiceAssignedToAccessibilityButton = componentName;
- userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ userState.setServiceAssignedToAccessibilityButtonLocked(componentName);
+ userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
return true;
}
- private boolean readUserRecommendedUiTimeoutSettingsLocked(UserState userState) {
+ private boolean readUserRecommendedUiTimeoutSettingsLocked(AccessibilityUserState userState) {
final int nonInteractiveUiTimeout = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, 0,
@@ -1919,10 +1899,10 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0,
userState.mUserId);
- if (nonInteractiveUiTimeout != userState.mUserNonInteractiveUiTimeout
- || interactiveUiTimeout != userState.mUserInteractiveUiTimeout) {
- userState.mUserNonInteractiveUiTimeout = nonInteractiveUiTimeout;
- userState.mUserInteractiveUiTimeout = interactiveUiTimeout;
+ if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked()
+ || interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) {
+ userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout);
+ userState.setUserInteractiveUiTimeoutLocked(interactiveUiTimeout);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
return true;
}
@@ -1936,22 +1916,22 @@
*
* @param userState
*/
- private void updateAccessibilityShortcutLocked(UserState userState) {
- if (userState.mServiceToEnableWithShortcut == null) {
+ private void updateAccessibilityShortcutLocked(AccessibilityUserState userState) {
+ if (userState.getServiceToEnableWithShortcutLocked() == null) {
return;
}
boolean shortcutServiceIsInstalled =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
- .containsKey(userState.mServiceToEnableWithShortcut);
+ .containsKey(userState.getServiceToEnableWithShortcutLocked());
for (int i = 0; !shortcutServiceIsInstalled && (i < userState.mInstalledServices.size());
i++) {
if (userState.mInstalledServices.get(i).getComponentName()
- .equals(userState.mServiceToEnableWithShortcut)) {
+ .equals(userState.getServiceToEnableWithShortcutLocked())) {
shortcutServiceIsInstalled = true;
}
}
if (!shortcutServiceIsInstalled) {
- userState.mServiceToEnableWithShortcut = null;
+ userState.setServiceToEnableWithShortcutLocked(null);
final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
@@ -1967,7 +1947,7 @@
}
private boolean canRequestAndRequestsTouchExplorationLocked(
- AccessibilityServiceConnection service, UserState userState) {
+ AccessibilityServiceConnection service, AccessibilityUserState userState) {
// Service not ready or cannot request the feature - well nothing to do.
if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) {
return false;
@@ -1997,7 +1977,7 @@
return false;
}
- private void updateMagnificationLocked(UserState userState) {
+ private void updateMagnificationLocked(AccessibilityUserState userState) {
if (userState.mUserId != mCurrentUserId) {
return;
}
@@ -2012,8 +1992,8 @@
// We would skip overlay display because it uses overlay window to simulate secondary
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
- if (userState.mIsDisplayMagnificationEnabled
- || userState.mIsNavBarMagnificationEnabled) {
+ if (userState.isDisplayMagnificationEnabledLocked()
+ || userState.isNavBarMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
getMagnificationController().register(display.getDisplayId());
@@ -2037,7 +2017,7 @@
* Returns whether the specified user has any services that are capable of
* controlling magnification.
*/
- private boolean userHasMagnificationServicesLocked(UserState userState) {
+ private boolean userHasMagnificationServicesLocked(AccessibilityUserState userState) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
final AccessibilityServiceConnection service = services.get(i);
@@ -2052,7 +2032,7 @@
* Returns whether the specified user has any services that are capable of
* controlling magnification and are actively listening for magnification updates.
*/
- private boolean userHasListeningMagnificationServicesLocked(UserState userState,
+ private boolean userHasListeningMagnificationServicesLocked(AccessibilityUserState userState,
int displayId) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
@@ -2065,7 +2045,7 @@
return false;
}
- private void updateFingerprintGestureHandling(UserState userState) {
+ private void updateFingerprintGestureHandling(AccessibilityUserState userState) {
final List<AccessibilityServiceConnection> services;
synchronized (mLock) {
services = userState.mBoundServices;
@@ -2097,7 +2077,7 @@
}
}
- private void updateAccessibilityButtonTargetsLocked(UserState userState) {
+ private void updateAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
@@ -2107,9 +2087,9 @@
}
}
- private void updateRecommendedUiTimeoutLocked(UserState userState) {
- int newNonInteractiveUiTimeout = userState.mUserNonInteractiveUiTimeout;
- int newInteractiveUiTimeout = userState.mUserInteractiveUiTimeout;
+ private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
+ int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
+ int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
// read from a11y services if user does not specify value
if (newNonInteractiveUiTimeout == 0 || newInteractiveUiTimeout == 0) {
int serviceNonInteractiveUiTimeout = 0;
@@ -2132,8 +2112,8 @@
newInteractiveUiTimeout = serviceInteractiveUiTimeout;
}
}
- userState.mNonInteractiveUiTimeout = newNonInteractiveUiTimeout;
- userState.mInteractiveUiTimeout = newInteractiveUiTimeout;
+ userState.setNonInteractiveUiTimeoutLocked(newNonInteractiveUiTimeout);
+ userState.setInteractiveUiTimeoutLocked(newInteractiveUiTimeout);
}
@GuardedBy("mLock")
@@ -2180,8 +2160,8 @@
final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
synchronized(mLock) {
- final UserState userState = getUserStateLocked(mCurrentUserId);
- final ComponentName serviceName = userState.mServiceToEnableWithShortcut;
+ final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
+ final ComponentName serviceName = userState.getServiceToEnableWithShortcutLocked();
if (serviceName == null) {
return;
}
@@ -2218,11 +2198,11 @@
"getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
}
synchronized(mLock) {
- final UserState userState = getUserStateLocked(mCurrentUserId);
- if (userState.mServiceToEnableWithShortcut == null) {
+ final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
+ if (userState.getServiceToEnableWithShortcutLocked() == null) {
return null;
}
- return userState.mServiceToEnableWithShortcut.flattenToString();
+ return userState.getServiceToEnableWithShortcutLocked().flattenToString();
}
}
@@ -2237,7 +2217,7 @@
userId);
setting.write(ComponentNameSet.add(setting.read(), componentName));
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
if (userState.mEnabledServices.add(componentName)) {
onUserStateChangedLocked(userState);
}
@@ -2254,7 +2234,7 @@
userId);
setting.write(ComponentNameSet.remove(setting.read(), componentName));
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
if (userState.mEnabledServices.remove(componentName)) {
onUserStateChangedLocked(userState);
}
@@ -2322,14 +2302,14 @@
@Override
public long getRecommendedTimeoutMillis() {
synchronized(mLock) {
- final UserState userState = getCurrentUserStateLocked();
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
return getRecommendedTimeoutMillisLocked(userState);
}
}
- private long getRecommendedTimeoutMillisLocked(UserState userState) {
- return IntPair.of(userState.mInteractiveUiTimeout,
- userState.mNonInteractiveUiTimeout);
+ private long getRecommendedTimeoutMillisLocked(AccessibilityUserState userState) {
+ return IntPair.of(userState.getInteractiveUiTimeoutLocked(),
+ userState.getNonInteractiveUiTimeoutLocked());
}
@Override
@@ -2338,78 +2318,20 @@
synchronized (mLock) {
pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
pw.println();
+ pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+ pw.println();
final int userCount = mUserStates.size();
for (int i = 0; i < userCount; i++) {
- UserState userState = mUserStates.valueAt(i);
- pw.append("User state[attributes:{id=" + userState.mUserId);
- pw.append(", currentUser=" + (userState.mUserId == mCurrentUserId));
- pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled);
- pw.append(", displayMagnificationEnabled="
- + userState.mIsDisplayMagnificationEnabled);
- pw.append(", navBarMagnificationEnabled="
- + userState.mIsNavBarMagnificationEnabled);
- pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled);
- pw.append(", nonInteractiveUiTimeout=" + userState.mNonInteractiveUiTimeout);
- pw.append(", interactiveUiTimeout=" + userState.mInteractiveUiTimeout);
- pw.append(", installedServiceCount=" + userState.mInstalledServices.size());
- if (mUiAutomationManager.isUiAutomationRunningLocked()) {
- pw.append(", ");
- mUiAutomationManager.dumpUiAutomationService(fd, pw, args);
- pw.println();
- }
- pw.append("}");
- pw.println();
- pw.append(" Bound services:{");
- final int serviceCount = userState.mBoundServices.size();
- for (int j = 0; j < serviceCount; j++) {
- if (j > 0) {
- pw.append(", ");
- pw.println();
- pw.append(" ");
- }
- AccessibilityServiceConnection service = userState.mBoundServices.get(j);
- service.dump(fd, pw, args);
- }
- pw.println("}");
- pw.append(" Enabled services:{");
- Iterator<ComponentName> it = userState.mEnabledServices.iterator();
- if (it.hasNext()) {
- ComponentName componentName = it.next();
- pw.append(componentName.toShortString());
- while (it.hasNext()) {
- componentName = it.next();
- pw.append(", ");
- pw.append(componentName.toShortString());
- }
- }
- pw.println("}");
- pw.append(" Binding services:{");
- it = userState.mBindingServices.iterator();
- if (it.hasNext()) {
- ComponentName componentName = it.next();
- pw.append(componentName.toShortString());
- while (it.hasNext()) {
- componentName = it.next();
- pw.append(", ");
- pw.append(componentName.toShortString());
- }
- }
- pw.println("}]");
+ mUserStates.valueAt(i).dump(fd, pw, args);
+ }
+ if (mUiAutomationManager.isUiAutomationRunningLocked()) {
+ mUiAutomationManager.dumpUiAutomationService(fd, pw, args);
pw.println();
}
mA11yWindowManager.dump(fd, pw, args);
}
}
- private void putSecureIntForUser(String key, int value, int userid) {
- final long identity = Binder.clearCallingIdentity();
- try {
- Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userid);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
//TODO remove after refactoring KeyEventDispatcherTest
final class MainHandler extends Handler {
public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8;
@@ -2446,7 +2368,7 @@
@Override
public void onClientChangeLocked(boolean serviceInfoChanged) {
- AccessibilityManagerService.UserState userState = getUserStateLocked(mCurrentUserId);
+ AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
onUserStateChangedLocked(userState);
if (serviceInfoChanged) {
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
@@ -2474,7 +2396,7 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- final UserState userState;
+ final AccessibilityUserState userState;
synchronized (mLock) {
userState = getCurrentUserStateLocked();
}
@@ -2593,7 +2515,7 @@
if (mInputFilter != null) {
mInputFilter.onDisplayChanged();
}
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0; i < services.size(); i++) {
@@ -2618,7 +2540,7 @@
if (mInputFilter != null) {
mInputFilter.onDisplayChanged();
}
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0; i < services.size(); i++) {
@@ -2658,7 +2580,8 @@
final String[] mPackageNames;
int mLastSentRelevantEventTypes;
- private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+ private Client(IAccessibilityManagerClient callback, int clientUid,
+ AccessibilityUserState userState) {
mCallback = callback;
mPackageNames = mPackageManager.getPackagesForUid(clientUid);
synchronized (mLock) {
@@ -2667,316 +2590,6 @@
}
}
- public class UserState {
- public final int mUserId;
-
- // Non-transient state.
-
- public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
- new RemoteCallbackList<>();
-
- // Transient state.
-
- public final ArrayList<AccessibilityServiceConnection> mBoundServices = new ArrayList<>();
-
- public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
- new HashMap<>();
-
- public final List<AccessibilityServiceInfo> mInstalledServices =
- new ArrayList<>();
-
- public final List<AccessibilityShortcutInfo> mInstalledShortcuts = new ArrayList<>();
-
- private final Set<ComponentName> mBindingServices = new HashSet<>();
-
- public final Set<ComponentName> mEnabledServices = new HashSet<>();
-
- public final Set<ComponentName> mTouchExplorationGrantedServices =
- new HashSet<>();
-
- public ComponentName mServiceChangingSoftKeyboardMode;
-
- public ComponentName mServiceToEnableWithShortcut;
-
- public int mLastSentClientState = -1;
- public int mNonInteractiveUiTimeout = 0;
- public int mInteractiveUiTimeout = 0;
-
- private int mSoftKeyboardShowMode = 0;
-
- public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
- public ComponentName mServiceAssignedToAccessibilityButton;
-
- public boolean mIsTouchExplorationEnabled;
- public boolean mIsTextHighContrastEnabled;
- public boolean mIsDisplayMagnificationEnabled;
- public boolean mIsNavBarMagnificationEnabled;
- public boolean mIsAutoclickEnabled;
- public boolean mIsPerformGesturesEnabled;
- public boolean mIsFilterKeyEventsEnabled;
- public int mUserNonInteractiveUiTimeout;
- public int mUserInteractiveUiTimeout;
-
- private boolean mBindInstantServiceAllowed;
-
- public UserState(int userId) {
- mUserId = userId;
- }
-
- public int getClientState() {
- int clientState = 0;
- final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
- || isHandlingAccessibilityEvents());
- if (a11yEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
- }
- // Touch exploration relies on enabled accessibility.
- if (a11yEnabled && mIsTouchExplorationEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
- }
- if (mIsTextHighContrastEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
- }
- return clientState;
- }
-
- public boolean isHandlingAccessibilityEvents() {
- return !mBoundServices.isEmpty() || !mBindingServices.isEmpty();
- }
-
- public void onSwitchToAnotherUserLocked() {
- // Unbind all services.
- unbindAllServicesLocked(this);
-
- // Clear service management state.
- mBoundServices.clear();
- mBindingServices.clear();
-
- // Clear event management state.
- mLastSentClientState = -1;
-
- // clear UI timeout
- mNonInteractiveUiTimeout = 0;
- mInteractiveUiTimeout = 0;
-
- // Clear state persisted in settings.
- mEnabledServices.clear();
- mTouchExplorationGrantedServices.clear();
- mIsTouchExplorationEnabled = false;
- mIsDisplayMagnificationEnabled = false;
- mIsNavBarMagnificationEnabled = false;
- mServiceAssignedToAccessibilityButton = null;
- mIsNavBarMagnificationAssignedToAccessibilityButton = false;
- mIsAutoclickEnabled = false;
- mUserNonInteractiveUiTimeout = 0;
- mUserInteractiveUiTimeout = 0;
- }
-
- public void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
- if (!mBoundServices.contains(serviceConnection)) {
- serviceConnection.onAdded();
- mBoundServices.add(serviceConnection);
- mComponentNameToServiceMap.put(serviceConnection.mComponentName, serviceConnection);
- scheduleNotifyClientsOfServicesStateChangeLocked(this);
- }
- }
-
- /**
- * Removes a service.
- * There are three states to a service here: off, bound, and binding.
- * This stops tracking the service as bound.
- *
- * @param serviceConnection The service.
- */
- public void removeServiceLocked(AccessibilityServiceConnection serviceConnection) {
- mBoundServices.remove(serviceConnection);
- serviceConnection.onRemoved();
- if ((mServiceChangingSoftKeyboardMode != null)
- && (mServiceChangingSoftKeyboardMode.equals(
- serviceConnection.getServiceInfo().getComponentName()))) {
- setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
- }
- // It may be possible to bind a service twice, which confuses the map. Rebuild the map
- // to make sure we can still reach a service
- mComponentNameToServiceMap.clear();
- for (int i = 0; i < mBoundServices.size(); i++) {
- AccessibilityServiceConnection boundClient = mBoundServices.get(i);
- mComponentNameToServiceMap.put(boundClient.mComponentName, boundClient);
- }
- scheduleNotifyClientsOfServicesStateChangeLocked(this);
- }
-
- /**
- * Make sure a services disconnected but still 'on' state is reflected in UserState
- * There are three states to a service here: off, bound, and binding.
- * This drops a service from a bound state, to the binding state.
- * The binding state describes the situation where a service is on, but not bound.
- *
- * @param serviceConnection The service.
- */
- public void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
- removeServiceLocked(serviceConnection);
- mBindingServices.add(serviceConnection.getComponentName());
- }
-
- public Set<ComponentName> getBindingServicesLocked() {
- return mBindingServices;
- }
-
- /**
- * Returns enabled service list.
- */
- public Set<ComponentName> getEnabledServicesLocked() {
- return mEnabledServices;
- }
-
- public int getSoftKeyboardShowMode() {
- return mSoftKeyboardShowMode;
- }
-
- /**
- * Set the soft keyboard mode. This mode is a bit odd, as it spans multiple settings.
- * The ACCESSIBILITY_SOFT_KEYBOARD_MODE setting can be checked by the rest of the system
- * to see if it should suppress showing the IME. The SHOW_IME_WITH_HARD_KEYBOARD setting
- * setting can be changed by the user, and prevents the system from suppressing the soft
- * keyboard when the hard keyboard is connected. The hard keyboard setting needs to defer
- * to the user's preference, if they have supplied one.
- *
- * @param newMode The new mode
- * @param requester The service requesting the change, so we can undo it when the
- * service stops. Set to null if something other than a service is forcing
- * the change.
- *
- * @return Whether or not the soft keyboard mode equals the new mode after the call
- */
- public boolean setSoftKeyboardModeLocked(int newMode, @Nullable ComponentName requester) {
- if ((newMode != SHOW_MODE_AUTO) && (newMode != SHOW_MODE_HIDDEN)
- && (newMode != SHOW_MODE_IGNORE_HARD_KEYBOARD))
- {
- Slog.w(LOG_TAG, "Invalid soft keyboard mode");
- return false;
- }
- if (mSoftKeyboardShowMode == newMode) return true;
-
- if (newMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- if (hasUserOverriddenHardKeyboardSettingLocked()) {
- // The user has specified a default for this setting
- return false;
- }
- // Save the original value. But don't do this if the value in settings is already
- // the new mode. That happens when we start up after a reboot, and we don't want
- // to overwrite the value we had from when we first started controlling the setting.
- if (getSoftKeyboardValueFromSettings() != SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- setOriginalHardKeyboardValue(
- Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0);
- }
- putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1, mUserId);
- } else if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
- getOriginalHardKeyboardValue() ? 1 : 0, mUserId);
- }
-
- saveSoftKeyboardValueToSettings(newMode);
- mSoftKeyboardShowMode = newMode;
- mServiceChangingSoftKeyboardMode = requester;
- notifySoftKeyboardShowModeChangedLocked(mSoftKeyboardShowMode);
- return true;
- }
-
- /**
- * If the settings are inconsistent with the internal state, make the internal state
- * match the settings.
- */
- public void reconcileSoftKeyboardModeWithSettingsLocked() {
- final ContentResolver cr = mContext.getContentResolver();
- final boolean showWithHardKeyboardSettings =
- Settings.Secure.getInt(cr, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0;
- if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- if (!showWithHardKeyboardSettings) {
- // The user has overridden the setting. Respect that and prevent further changes
- // to this behavior.
- setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
- setUserOverridesHardKeyboardSettingLocked();
- }
- }
-
- // If the setting and the internal state are out of sync, set both to default
- if (getSoftKeyboardValueFromSettings() != mSoftKeyboardShowMode)
- {
- Slog.e(LOG_TAG,
- "Show IME setting inconsistent with internal state. Overwriting");
- setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- SHOW_MODE_AUTO, mUserId);
- }
- }
-
- private void setUserOverridesHardKeyboardSettingLocked() {
- final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- softKeyboardSetting | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
- mUserId);
- }
-
- private boolean hasUserOverriddenHardKeyboardSettingLocked() {
- final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- return (softKeyboardSetting & SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN)
- != 0;
- }
-
- private void setOriginalHardKeyboardValue(boolean originalHardKeyboardValue) {
- final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- final int newSoftKeyboardSetting = oldSoftKeyboardSetting
- & (~SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE)
- | ((originalHardKeyboardValue) ? SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE : 0);
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- newSoftKeyboardSetting, mUserId);
- }
-
- private void saveSoftKeyboardValueToSettings(int softKeyboardShowMode) {
- final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- final int newSoftKeyboardSetting = oldSoftKeyboardSetting & (~SHOW_MODE_MASK)
- | softKeyboardShowMode;
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- newSoftKeyboardSetting, mUserId);
- }
-
- private int getSoftKeyboardValueFromSettings() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- SHOW_MODE_AUTO) & SHOW_MODE_MASK;
- }
-
- private boolean getOriginalHardKeyboardValue() {
- return (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0)
- & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
- }
-
- public boolean getBindInstantServiceAllowed() {
- synchronized (mLock) {
- return mBindInstantServiceAllowed;
- }
- }
-
- public void setBindInstantServiceAllowed(boolean allowed) {
- synchronized (mLock) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
- "setBindInstantServiceAllowed");
- if (allowed) {
- mBindInstantServiceAllowed = allowed;
- onUserStateChangedLocked(this);
- }
- }
- }
- }
-
private final class AccessibilityContentObserver extends ContentObserver {
private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor(
@@ -3057,7 +2670,7 @@
synchronized (mLock) {
// Profiles share the accessibility state of the parent. Therefore,
// we are checking for changes only the parent settings.
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (mTouchExplorationEnabledUri.equals(uri)) {
if (readTouchExplorationEnabledSettingLocked(userState)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index e7f3ccc..d154060 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -35,7 +35,6 @@
import android.util.Slog;
import android.view.Display;
-import com.android.server.accessibility.AccessibilityManagerService.UserState;
import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
@@ -52,13 +51,13 @@
class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
private static final String LOG_TAG = "AccessibilityServiceConnection";
/*
- Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
- and binding services. These are freed on user changes, but just in case it somehow gets lost
- the weak reference will let the memory get GCed.
+ Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps
+ lists of bound and binding services. These are freed on user changes, but just in case it
+ somehow gets lost the weak reference will let the memory get GCed.
Having the reference be null when being called is a very bad sign, but we check the condition.
*/
- final WeakReference<UserState> mUserStateWeakReference;
+ final WeakReference<AccessibilityUserState> mUserStateWeakReference;
final Intent mIntent;
private final Handler mMainHandler;
@@ -66,7 +65,7 @@
private boolean mWasConnectedAndDied;
- public AccessibilityServiceConnection(UserState userState, Context context,
+ AccessibilityServiceConnection(AccessibilityUserState userState, Context context,
ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
@@ -74,7 +73,7 @@
SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm) {
super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm);
- mUserStateWeakReference = new WeakReference<UserState>(userState);
+ mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState);
mIntent = new Intent().setComponent(mComponentName);
mMainHandler = mainHandler;
mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
@@ -89,13 +88,13 @@
}
public void bindLocked() {
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
final long identity = Binder.clearCallingIdentity();
try {
int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS;
- if (userState.getBindInstantServiceAllowed()) {
+ if (userState.getBindInstantServiceAllowedLocked()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
if (mService == null && mContext.bindServiceAsUser(
@@ -109,7 +108,7 @@
public void unbindLocked() {
mContext.unbindService(this);
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.removeServiceLocked(this);
mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
@@ -123,7 +122,7 @@
@Override
public void disableSelf() {
synchronized (mLock) {
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
if (userState.getEnabledServicesLocked().remove(mComponentName)) {
final long identity = Binder.clearCallingIdentity();
@@ -156,7 +155,7 @@
}
}
mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.addServiceLocked(this);
mSystemSupport.onClientChangeLocked(false);
@@ -177,7 +176,7 @@
private void initializeService() {
IAccessibilityServiceClient serviceInterface = null;
synchronized (mLock) {
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
if (bindingServices.contains(mComponentName) || mWasConnectedAndDied) {
@@ -240,7 +239,7 @@
if (!hasRightsToCurrentUserLocked()) {
return false;
}
- final UserState userState = mUserStateWeakReference.get();
+ final AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return false;
return userState.setSoftKeyboardModeLocked(showMode, mComponentName);
}
@@ -248,8 +247,8 @@
@Override
public int getSoftKeyboardShowMode() {
- final UserState userState = mUserStateWeakReference.get();
- return (userState != null) ? userState.getSoftKeyboardShowMode() : 0;
+ final AccessibilityUserState userState = mUserStateWeakReference.get();
+ return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
}
@Override
@@ -258,7 +257,7 @@
if (!hasRightsToCurrentUserLocked()) {
return false;
}
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
return (userState != null) && isAccessibilityButtonAvailableLocked(userState);
}
}
@@ -273,7 +272,7 @@
return;
}
mWasConnectedAndDied = true;
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState != null) {
userState.serviceDisconnectedLocked(this);
}
@@ -283,7 +282,7 @@
}
}
- public boolean isAccessibilityButtonAvailableLocked(UserState userState) {
+ public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
// If the service does not request the accessibility button, it isn't available
if (!mRequestAccessibilityButton) {
return false;
@@ -295,8 +294,8 @@
}
// If magnification is on and assigned to the accessibility button, services cannot be
- if (userState.mIsNavBarMagnificationEnabled
- && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if (userState.isNavBarMagnificationEnabledLocked()
+ && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
return false;
}
@@ -314,13 +313,14 @@
return true;
} else {
// With more than one active service, we derive the target from the user's settings
- if (userState.mServiceAssignedToAccessibilityButton == null) {
+ if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
// If the user has not made an assignment, we treat the button as available to
// all services until the user interacts with the button to make an assignment
return true;
} else {
// If an assignment was made, it defines availability
- return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton);
+ return mComponentName.equals(
+ userState.getServiceAssignedToAccessibilityButtonLocked());
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
new file mode 100644
index 0000000..69f1e0e
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
+
+import android.accessibilityservice.AccessibilityService.SoftKeyboardShowMode;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityShortcutInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class that hold states and settings per user and share between
+ * {@link AccessibilityManagerService} and {@link AccessibilityServiceConnection}.
+ */
+class AccessibilityUserState {
+ private static final String LOG_TAG = AccessibilityUserState.class.getSimpleName();
+
+ final int mUserId;
+
+ // Non-transient state.
+
+ final RemoteCallbackList<IAccessibilityManagerClient> mUserClients = new RemoteCallbackList<>();
+
+ // Transient state.
+
+ final ArrayList<AccessibilityServiceConnection> mBoundServices = new ArrayList<>();
+
+ final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
+ new HashMap<>();
+
+ final List<AccessibilityServiceInfo> mInstalledServices = new ArrayList<>();
+
+ final List<AccessibilityShortcutInfo> mInstalledShortcuts = new ArrayList<>();
+
+ final Set<ComponentName> mBindingServices = new HashSet<>();
+
+ final Set<ComponentName> mEnabledServices = new HashSet<>();
+
+ final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
+
+ private final ServiceInfoChangeListener mServiceInfoChangeListener;
+
+ private ComponentName mServiceAssignedToAccessibilityButton;
+
+ private ComponentName mServiceChangingSoftKeyboardMode;
+
+ private ComponentName mServiceToEnableWithShortcut;
+
+ private boolean mBindInstantServiceAllowed;
+ private boolean mIsAutoclickEnabled;
+ private boolean mIsDisplayMagnificationEnabled;
+ private boolean mIsFilterKeyEventsEnabled;
+ private boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
+ private boolean mIsNavBarMagnificationEnabled;
+ private boolean mIsPerformGesturesEnabled;
+ private boolean mIsTextHighContrastEnabled;
+ private boolean mIsTouchExplorationEnabled;
+ private int mUserInteractiveUiTimeout;
+ private int mUserNonInteractiveUiTimeout;
+ private int mNonInteractiveUiTimeout = 0;
+ private int mInteractiveUiTimeout = 0;
+ private int mLastSentClientState = -1;
+
+ private Context mContext;
+
+ @SoftKeyboardShowMode
+ private int mSoftKeyboardShowMode = SHOW_MODE_AUTO;
+
+ interface ServiceInfoChangeListener {
+ void onServiceInfoChangedLocked(AccessibilityUserState userState);
+ }
+
+ AccessibilityUserState(int userId, @NonNull Context context,
+ @NonNull ServiceInfoChangeListener serviceInfoChangeListener) {
+ mUserId = userId;
+ mContext = context;
+ mServiceInfoChangeListener = serviceInfoChangeListener;
+ }
+
+ boolean isHandlingAccessibilityEventsLocked() {
+ return !mBoundServices.isEmpty() || !mBindingServices.isEmpty();
+ }
+
+ void onSwitchToAnotherUserLocked() {
+ // Unbind all services.
+ unbindAllServicesLocked();
+
+ // Clear service management state.
+ mBoundServices.clear();
+ mBindingServices.clear();
+
+ // Clear event management state.
+ mLastSentClientState = -1;
+
+ // clear UI timeout
+ mNonInteractiveUiTimeout = 0;
+ mInteractiveUiTimeout = 0;
+
+ // Clear state persisted in settings.
+ mEnabledServices.clear();
+ mTouchExplorationGrantedServices.clear();
+ mIsTouchExplorationEnabled = false;
+ mIsDisplayMagnificationEnabled = false;
+ mIsNavBarMagnificationEnabled = false;
+ mServiceAssignedToAccessibilityButton = null;
+ mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ mIsAutoclickEnabled = false;
+ mUserNonInteractiveUiTimeout = 0;
+ mUserInteractiveUiTimeout = 0;
+ }
+
+ void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
+ if (!mBoundServices.contains(serviceConnection)) {
+ serviceConnection.onAdded();
+ mBoundServices.add(serviceConnection);
+ mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection);
+ mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
+ }
+ }
+
+ /**
+ * Removes a service.
+ * There are three states to a service here: off, bound, and binding.
+ * This stops tracking the service as bound.
+ *
+ * @param serviceConnection The service.
+ */
+ void removeServiceLocked(AccessibilityServiceConnection serviceConnection) {
+ mBoundServices.remove(serviceConnection);
+ serviceConnection.onRemoved();
+ if ((mServiceChangingSoftKeyboardMode != null)
+ && (mServiceChangingSoftKeyboardMode.equals(
+ serviceConnection.getServiceInfo().getComponentName()))) {
+ setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+ }
+ // It may be possible to bind a service twice, which confuses the map. Rebuild the map
+ // to make sure we can still reach a service
+ mComponentNameToServiceMap.clear();
+ for (int i = 0; i < mBoundServices.size(); i++) {
+ AccessibilityServiceConnection boundClient = mBoundServices.get(i);
+ mComponentNameToServiceMap.put(boundClient.getComponentName(), boundClient);
+ }
+ mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
+ }
+
+ /**
+ * Make sure a services disconnected but still 'on' state is reflected in AccessibilityUserState
+ * There are three states to a service here: off, bound, and binding.
+ * This drops a service from a bound state, to the binding state.
+ * The binding state describes the situation where a service is on, but not bound.
+ *
+ * @param serviceConnection The service.
+ */
+ void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
+ removeServiceLocked(serviceConnection);
+ mBindingServices.add(serviceConnection.getComponentName());
+ }
+
+ /**
+ * Set the soft keyboard mode. This mode is a bit odd, as it spans multiple settings.
+ * The ACCESSIBILITY_SOFT_KEYBOARD_MODE setting can be checked by the rest of the system
+ * to see if it should suppress showing the IME. The SHOW_IME_WITH_HARD_KEYBOARD setting
+ * setting can be changed by the user, and prevents the system from suppressing the soft
+ * keyboard when the hard keyboard is connected. The hard keyboard setting needs to defer
+ * to the user's preference, if they have supplied one.
+ *
+ * @param newMode The new mode
+ * @param requester The service requesting the change, so we can undo it when the
+ * service stops. Set to null if something other than a service is forcing
+ * the change.
+ *
+ * @return Whether or not the soft keyboard mode equals the new mode after the call
+ */
+ boolean setSoftKeyboardModeLocked(@SoftKeyboardShowMode int newMode,
+ @Nullable ComponentName requester) {
+ if ((newMode != SHOW_MODE_AUTO)
+ && (newMode != SHOW_MODE_HIDDEN)
+ && (newMode != SHOW_MODE_IGNORE_HARD_KEYBOARD)) {
+ Slog.w(LOG_TAG, "Invalid soft keyboard mode");
+ return false;
+ }
+ if (mSoftKeyboardShowMode == newMode) {
+ return true;
+ }
+
+ if (newMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ if (hasUserOverriddenHardKeyboardSetting()) {
+ // The user has specified a default for this setting
+ return false;
+ }
+ // Save the original value. But don't do this if the value in settings is already
+ // the new mode. That happens when we start up after a reboot, and we don't want
+ // to overwrite the value we had from when we first started controlling the setting.
+ if (getSoftKeyboardValueFromSettings() != SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ setOriginalHardKeyboardValue(getSecureIntForUser(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mUserId) != 0);
+ }
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1, mUserId);
+ } else if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ getOriginalHardKeyboardValue() ? 1 : 0, mUserId);
+ }
+
+ saveSoftKeyboardValueToSettings(newMode);
+ mSoftKeyboardShowMode = newMode;
+ mServiceChangingSoftKeyboardMode = requester;
+ for (int i = mBoundServices.size() - 1; i >= 0; i--) {
+ final AccessibilityServiceConnection service = mBoundServices.get(i);
+ service.notifySoftKeyboardShowModeChangedLocked(mSoftKeyboardShowMode);
+ }
+ return true;
+ }
+
+ @SoftKeyboardShowMode
+ int getSoftKeyboardShowModeLocked() {
+ return mSoftKeyboardShowMode;
+ }
+
+ /**
+ * If the settings are inconsistent with the internal state, make the internal state
+ * match the settings.
+ */
+ void reconcileSoftKeyboardModeWithSettingsLocked() {
+ final boolean showWithHardKeyboardSettings =
+ getSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mUserId) != 0;
+ if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ if (!showWithHardKeyboardSettings) {
+ // The user has overridden the setting. Respect that and prevent further changes
+ // to this behavior.
+ setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+ setUserOverridesHardKeyboardSetting();
+ }
+ }
+
+ // If the setting and the internal state are out of sync, set both to default
+ if (getSoftKeyboardValueFromSettings() != mSoftKeyboardShowMode) {
+ Slog.e(LOG_TAG, "Show IME setting inconsistent with internal state. Overwriting");
+ setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_AUTO, mUserId);
+ }
+ }
+
+ boolean getBindInstantServiceAllowedLocked() {
+ return mBindInstantServiceAllowed;
+ }
+
+ /* Need to have a permission check on callee */
+ void setBindInstantServiceAllowedLocked(boolean allowed) {
+ mBindInstantServiceAllowed = allowed;
+ }
+
+ Set<ComponentName> getBindingServicesLocked() {
+ return mBindingServices;
+ }
+
+ /**
+ * Returns enabled service list.
+ */
+ Set<ComponentName> getEnabledServicesLocked() {
+ return mEnabledServices;
+ }
+
+ List<AccessibilityServiceConnection> getBoundServicesLocked() {
+ return mBoundServices;
+ }
+
+ int getClientStateLocked(boolean isUiAutomationRunning) {
+ int clientState = 0;
+ final boolean a11yEnabled = isUiAutomationRunning
+ || isHandlingAccessibilityEventsLocked();
+ if (a11yEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+ }
+ // Touch exploration relies on enabled accessibility.
+ if (a11yEnabled && mIsTouchExplorationEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+ }
+ if (mIsTextHighContrastEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
+ }
+ return clientState;
+ }
+
+ private void setUserOverridesHardKeyboardSetting() {
+ final int softKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ softKeyboardSetting | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
+ mUserId);
+ }
+
+ private boolean hasUserOverriddenHardKeyboardSetting() {
+ final int softKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ return (softKeyboardSetting & SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN)
+ != 0;
+ }
+
+ private void setOriginalHardKeyboardValue(boolean originalHardKeyboardValue) {
+ final int oldSoftKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ final int newSoftKeyboardSetting = oldSoftKeyboardSetting
+ & (~SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE)
+ | ((originalHardKeyboardValue) ? SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE : 0);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ newSoftKeyboardSetting, mUserId);
+ }
+
+ private void saveSoftKeyboardValueToSettings(int softKeyboardShowMode) {
+ final int oldSoftKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ final int newSoftKeyboardSetting = oldSoftKeyboardSetting & (~SHOW_MODE_MASK)
+ | softKeyboardShowMode;
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ newSoftKeyboardSetting, mUserId);
+ }
+
+ private int getSoftKeyboardValueFromSettings() {
+ return getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId)
+ & SHOW_MODE_MASK;
+ }
+
+ private boolean getOriginalHardKeyboardValue() {
+ return (getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId)
+ & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
+ }
+
+ private void unbindAllServicesLocked() {
+ final List<AccessibilityServiceConnection> services = mBoundServices;
+ for (int count = services.size(); count > 0; count--) {
+ // When the service is unbound, it disappears from the list, so there's no need to
+ // keep track of the index
+ services.get(0).unbindLocked();
+ }
+ }
+
+ private int getSecureIntForUser(String key, int def, int userId) {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(), key, def, userId);
+ }
+
+ private void putSecureIntForUser(String key, int value, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.append("User state[");
+ pw.println();
+ pw.append(" attributes:{id=").append(String.valueOf(mUserId));
+ pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
+ pw.append(", displayMagnificationEnabled=").append(String.valueOf(
+ mIsDisplayMagnificationEnabled));
+ pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
+ mIsNavBarMagnificationEnabled));
+ pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
+ pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
+ pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
+ pw.append(", installedServiceCount=").append(String.valueOf(mInstalledServices.size()));
+ pw.append("}");
+ pw.println();
+ pw.append(" Bound services:{");
+ final int serviceCount = mBoundServices.size();
+ for (int j = 0; j < serviceCount; j++) {
+ if (j > 0) {
+ pw.append(", ");
+ pw.println();
+ pw.append(" ");
+ }
+ AccessibilityServiceConnection service = mBoundServices.get(j);
+ service.dump(fd, pw, args);
+ }
+ pw.println("}");
+ pw.append(" Enabled services:{");
+ Iterator<ComponentName> it = mEnabledServices.iterator();
+ if (it.hasNext()) {
+ ComponentName componentName = it.next();
+ pw.append(componentName.toShortString());
+ while (it.hasNext()) {
+ componentName = it.next();
+ pw.append(", ");
+ pw.append(componentName.toShortString());
+ }
+ }
+ pw.println("}");
+ pw.append(" Binding services:{");
+ it = mBindingServices.iterator();
+ if (it.hasNext()) {
+ ComponentName componentName = it.next();
+ pw.append(componentName.toShortString());
+ while (it.hasNext()) {
+ componentName = it.next();
+ pw.append(", ");
+ pw.append(componentName.toShortString());
+ }
+ }
+ pw.println("}]");
+ }
+
+ public boolean isAutoclickEnabledLocked() {
+ return mIsAutoclickEnabled;
+ }
+
+ public void setAutoclickEnabledLocked(boolean enabled) {
+ mIsAutoclickEnabled = enabled;
+ }
+
+ public boolean isDisplayMagnificationEnabledLocked() {
+ return mIsDisplayMagnificationEnabled;
+ }
+
+ public void setDisplayMagnificationEnabledLocked(boolean enabled) {
+ mIsDisplayMagnificationEnabled = enabled;
+ }
+
+ public boolean isFilterKeyEventsEnabledLocked() {
+ return mIsFilterKeyEventsEnabled;
+ }
+
+ public void setFilterKeyEventsEnabledLocked(boolean enabled) {
+ mIsFilterKeyEventsEnabled = enabled;
+ }
+
+ public int getInteractiveUiTimeoutLocked() {
+ return mInteractiveUiTimeout;
+ }
+
+ public void setInteractiveUiTimeoutLocked(int timeout) {
+ mInteractiveUiTimeout = timeout;
+ }
+
+ public int getLastSentClientStateLocked() {
+ return mLastSentClientState;
+ }
+
+ public void setLastSentClientStateLocked(int state) {
+ mLastSentClientState = state;
+ }
+
+ public boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
+ return mIsNavBarMagnificationAssignedToAccessibilityButton;
+ }
+
+ public void setNavBarMagnificationAssignedToAccessibilityButtonLocked(boolean assigned) {
+ mIsNavBarMagnificationAssignedToAccessibilityButton = assigned;
+ }
+
+ public boolean isNavBarMagnificationEnabledLocked() {
+ return mIsNavBarMagnificationEnabled;
+ }
+
+ public void setNavBarMagnificationEnabledLocked(boolean enabled) {
+ mIsNavBarMagnificationEnabled = enabled;
+ }
+
+ public int getNonInteractiveUiTimeoutLocked() {
+ return mNonInteractiveUiTimeout;
+ }
+
+ public void setNonInteractiveUiTimeoutLocked(int timeout) {
+ mNonInteractiveUiTimeout = timeout;
+ }
+
+ public boolean isPerformGesturesEnabledLocked() {
+ return mIsPerformGesturesEnabled;
+ }
+
+ public void setPerformGesturesEnabledLocked(boolean enabled) {
+ mIsPerformGesturesEnabled = enabled;
+ }
+
+ public ComponentName getServiceAssignedToAccessibilityButtonLocked() {
+ return mServiceAssignedToAccessibilityButton;
+ }
+
+ public void setServiceAssignedToAccessibilityButtonLocked(ComponentName componentName) {
+ mServiceAssignedToAccessibilityButton = componentName;
+ }
+
+ public ComponentName getServiceChangingSoftKeyboardModeLocked() {
+ return mServiceChangingSoftKeyboardMode;
+ }
+
+ public void setServiceChangingSoftKeyboardModeLocked(
+ ComponentName serviceChangingSoftKeyboardMode) {
+ mServiceChangingSoftKeyboardMode = serviceChangingSoftKeyboardMode;
+ }
+
+ public ComponentName getServiceToEnableWithShortcutLocked() {
+ return mServiceToEnableWithShortcut;
+ }
+
+ public void setServiceToEnableWithShortcutLocked(ComponentName componentName) {
+ mServiceToEnableWithShortcut = componentName;
+ }
+
+ public boolean isTextHighContrastEnabledLocked() {
+ return mIsTextHighContrastEnabled;
+ }
+
+ public void setTextHighContrastEnabledLocked(boolean enabled) {
+ mIsTextHighContrastEnabled = enabled;
+ }
+
+ public boolean isTouchExplorationEnabledLocked() {
+ return mIsTouchExplorationEnabled;
+ }
+
+ public void setTouchExplorationEnabledLocked(boolean enabled) {
+ mIsTouchExplorationEnabled = enabled;
+ }
+
+ public int getUserInteractiveUiTimeoutLocked() {
+ return mUserInteractiveUiTimeout;
+ }
+
+ public void setUserInteractiveUiTimeoutLocked(int timeout) {
+ mUserInteractiveUiTimeout = timeout;
+ }
+
+ public int getUserNonInteractiveUiTimeoutLocked() {
+ return mUserNonInteractiveUiTimeout;
+ }
+
+ public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
+ mUserNonInteractiveUiTimeout = timeout;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index b35300c..7d129ea 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -286,7 +286,7 @@
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
- window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
+ window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
window.setCloseOnTouchOutside(true);
diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index de48f4b..30ce4cf 100644
--- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -93,8 +93,7 @@
mTransportWhitelist = Preconditions.checkNotNull(whitelist);
mCurrentTransportName = selectedTransport;
mTransportStats = new TransportStats();
- mTransportClientManager = TransportClientManager.createEncryptingClientManager(mUserId,
- context, mTransportStats);
+ mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats);
}
@VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index e5e11ea..ac006df 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -239,7 +239,6 @@
private final KeyValueBackupReporter mReporter;
private final OnTaskFinishedListener mTaskFinishedListener;
private final boolean mUserInitiated;
- private final boolean mNonIncremental;
private final int mCurrentOpToken;
private final int mUserId;
private final File mStateDirectory;
@@ -264,6 +263,7 @@
// and at least one of the packages had data. Used to avoid updating current token for
// empty backups.
private boolean mHasDataToBackup;
+ private boolean mNonIncremental;
/**
* This {@link ConditionVariable} is used to signal that the cancel operation has been
@@ -412,6 +412,11 @@
try {
IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.startTask()");
String transportName = transport.name();
+ if (transportName.contains("EncryptedLocalTransport")) {
+ // Temporary code for EiTF POC. Only supports non-incremental backups.
+ mNonIncremental = true;
+ }
+
mReporter.onTransportReady(transportName);
// If we haven't stored PM metadata yet, we must initialize the transport.
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 1d666ad..dc24cff 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -59,6 +59,8 @@
public static final int PACKAGE_CONFIGURATOR = 9;
public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10;
public static final int PACKAGE_APP_PREDICTOR = 11;
+ public static final int PACKAGE_TELEPHONY = 12;
+ public static final int PACKAGE_WIFI = 13;
@IntDef(value = {
PACKAGE_SYSTEM,
PACKAGE_SETUP_WIZARD,
@@ -72,6 +74,8 @@
PACKAGE_CONFIGURATOR,
PACKAGE_INCIDENT_REPORT_APPROVER,
PACKAGE_APP_PREDICTOR,
+ PACKAGE_TELEPHONY,
+ PACKAGE_WIFI,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {}
@@ -546,10 +550,11 @@
*/
public abstract boolean isResolveActivityComponent(@NonNull ComponentInfo component);
+
/**
- * Returns the package name for a known package.
+ * Returns a list of package names for a known package
*/
- public abstract @Nullable String getKnownPackageName(
+ public abstract @NonNull String[] getKnownPackageNames(
@KnownPackage int knownPackage, int userId);
/**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e0f60b4..0bb72cb 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1149,7 +1149,6 @@
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
- netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setSingleUid(uid);
return netCap;
@@ -1159,7 +1158,6 @@
int transportType, NetworkRequest.Type type) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
- netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
if (transportType > -1) {
netCap.addTransportType(transportType);
}
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index b6e619e..44a8234 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
@@ -298,7 +299,8 @@
* @param packageName name of requesting package
* @return true if callback is successfully added, false otherwise
*/
- public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
+ public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
+ @NonNull String listenerIdentity) {
mContext.enforceCallingPermission(
android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to access hardware batching");
@@ -316,7 +318,8 @@
}
CallerIdentity callerIdentity =
- new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+ new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
+ listenerIdentity);
synchronized (mGnssBatchingLock) {
mGnssBatchingCallback = callback;
mGnssBatchingDeathCallback =
@@ -494,7 +497,7 @@
private <TListener extends IInterface> boolean addGnssDataListenerLocked(
TListener listener,
String packageName,
- String listenerName,
+ @NonNull String listenerIdentifier,
RemoteListenerHelper<TListener> gnssDataProvider,
ArrayMap<IBinder,
LinkedListener<TListener>> gnssDataListeners,
@@ -513,10 +516,11 @@
}
CallerIdentity callerIdentity =
- new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+ new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
+ listenerIdentifier);
LinkedListener<TListener> linkedListener =
new LocationManagerServiceUtils.LinkedListener<>(
- listener, listenerName, callerIdentity, binderDeathCallback);
+ listener, listenerIdentifier, callerIdentity, binderDeathCallback);
IBinder binder = listener.asBinder();
if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) {
return false;
@@ -606,7 +610,7 @@
return addGnssDataListenerLocked(
listener,
packageName,
- "GnssStatusListener",
+ "Gnss status",
mGnssStatusProvider,
mGnssStatusListeners,
this::unregisterGnssStatusCallback);
@@ -632,12 +636,13 @@
* @return true if listener is successfully added, false otherwise
*/
public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName) {
+ IGnssMeasurementsListener listener, String packageName,
+ @NonNull String listenerIdentifier) {
synchronized (mGnssMeasurementsListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
- "GnssMeasurementsListener",
+ listenerIdentifier,
mGnssMeasurementsProvider,
mGnssMeasurementsListeners,
this::removeGnssMeasurementsListener);
@@ -689,12 +694,13 @@
* @return true if listener is successfully added, false otherwise
*/
public boolean addGnssNavigationMessageListener(
- IGnssNavigationMessageListener listener, String packageName) {
+ IGnssNavigationMessageListener listener, String packageName,
+ @NonNull String listenerIdentifier) {
synchronized (mGnssNavigationMessageListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
- "GnssNavigationMessageListener",
+ listenerIdentifier,
mGnssNavigationMessageProvider,
mGnssNavigationMessageListeners,
this::removeGnssNavigationMessageListener);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 09f62ff..aa22feb 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1223,8 +1223,10 @@
PowerManager.WakeLock mWakeLock;
private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps) {
- super(new CallerIdentity(uid, pid, packageName), "LocationListener");
+ String packageName, WorkSource workSource, boolean hideFromAppOps,
+ @NonNull String listenerIdentifier) {
+ super(new CallerIdentity(uid, pid, packageName, listenerIdentifier),
+ "LocationListener");
mListener = listener;
mPendingIntent = intent;
if (listener != null) {
@@ -1532,9 +1534,12 @@
}
@Override
- public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
+ public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
+ String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
- callback, packageName);
+ callback, packageName, listenerIdentifier);
}
@Override
@@ -1696,11 +1701,12 @@
}
}
- private boolean reportLocationAccessNoThrow(
- int pid, int uid, String packageName, int allowedResolutionLevel) {
+ private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
+ int allowedResolutionLevel, @Nullable String message) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
- if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+ if (mAppOps.noteOpNoThrow(op, uid, packageName, message)
+ != AppOpsManager.MODE_ALLOWED) {
return false;
}
}
@@ -2133,12 +2139,13 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps) {
+ String packageName, WorkSource workSource, boolean hideFromAppOps,
+ @NonNull String listenerIdentifier) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
if (!receiver.linkToListenerDeathNotificationLocked(
receiver.getListener().asBinder())) {
return null;
@@ -2150,11 +2157,11 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
- WorkSource workSource, boolean hideFromAppOps) {
+ WorkSource workSource, boolean hideFromAppOps, @NonNull String listenerIdentifier) {
Receiver receiver = mReceivers.get(intent);
if (receiver == null) {
receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
mReceivers.put(intent, receiver);
}
return receiver;
@@ -2216,7 +2223,9 @@
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
- PendingIntent intent, String packageName) {
+ PendingIntent intent, String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
synchronized (mLock) {
if (request == null) request = DEFAULT_LOCATION_REQUEST;
checkPackageName(packageName);
@@ -2271,10 +2280,10 @@
Receiver receiver;
if (intent != null) {
receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
} else {
receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
}
requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
} finally {
@@ -2343,9 +2352,9 @@
synchronized (mLock) {
Receiver receiver;
if (intent != null) {
- receiver = getReceiverLocked(intent, pid, uid, packageName, null, false);
+ receiver = getReceiverLocked(intent, pid, uid, packageName, null, false, "");
} else {
- receiver = getReceiverLocked(listener, pid, uid, packageName, null, false);
+ receiver = getReceiverLocked(listener, pid, uid, packageName, null, false, "");
}
long identity = Binder.clearCallingIdentity();
@@ -2464,8 +2473,8 @@
}
// Don't report location access if there is no last location to deliver.
if (lastLocation != null) {
- if (!reportLocationAccessNoThrow(
- pid, uid, packageName, allowedResolutionLevel)) {
+ if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel,
+ null)) {
if (D) {
Log.d(TAG, "not returning last loc for no op app: " + packageName);
}
@@ -2519,7 +2528,9 @@
@Override
public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
- String packageName) {
+ String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
if (request == null) request = DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
@@ -2564,9 +2575,8 @@
mActivityManager.getPackageImportance(packageName));
}
- mGeofenceManager.addFence(sanitizedRequest, geofence, intent,
- allowedResolutionLevel,
- uid, packageName);
+ mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
+ uid, packageName, listenerIdentifier);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2613,10 +2623,13 @@
}
@Override
- public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName) {
+ public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
+ String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssMeasurementsListener(listener, packageName);
+ : mGnssManagerService.addGnssMeasurementsListener(listener, packageName,
+ listenerIdentifier);
}
@Override
@@ -2643,10 +2656,13 @@
}
@Override
- public boolean addGnssNavigationMessageListener(
- IGnssNavigationMessageListener listener, String packageName) {
+ public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
+ String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName);
+ : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
+ listenerIdentifier);
}
@Override
@@ -2719,12 +2735,21 @@
return true;
}
}
-
return false;
}
}
@Override
+ public List<String> getProviderPackages(String providerName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG + " permission required");
+ synchronized (mLock) {
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
+ }
+ }
+
+ @Override
public void setExtraLocationControllerPackage(String packageName) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
Manifest.permission.LOCATION_HARDWARE + " permission required");
@@ -2943,7 +2968,8 @@
receiver.mCallerIdentity.mPid,
receiver.mCallerIdentity.mUid,
receiver.mCallerIdentity.mPackageName,
- receiver.mAllowedResolutionLevel)) {
+ receiver.mAllowedResolutionLevel,
+ "Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
if (D) {
Log.d(TAG, "skipping loc update for no op app: "
+ receiver.mCallerIdentity.mPackageName);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0f8a3b5..447ed59 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -385,7 +385,7 @@
mContext = context;
mBatteryStats = BatteryStatsService.getService();
- int numPhones = TelephonyManager.getDefault().getMaxPhoneCount();
+ int numPhones = TelephonyManager.getDefault().getSupportedModemCount();
if (DBG) log("TelephonyRegistry: ctor numPhones=" + numPhones);
mNumPhones = numPhones;
mCallState = new int[numPhones];
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8c672a1..55cd933 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -900,7 +900,7 @@
// The other observer methods are unused
@Override
- public void onIntentStarted(Intent intent) {
+ public void onIntentStarted(Intent intent, long timestampNs) {
}
@Override
@@ -912,7 +912,11 @@
}
@Override
- public void onActivityLaunchFinished(byte[] finalActivity) {
+ public void onActivityLaunchFinished(byte[] finalActivity, long timestampNs) {
+ }
+
+ @Override
+ public void onReportFullyDrawn(byte[] finalActivity, long timestampNs) {
}
};
@@ -8658,7 +8662,7 @@
lp.format = v.getBackground().getOpacity();
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
((WindowManager)mContext.getSystemService(
Context.WINDOW_SERVICE)).addView(v, lp);
}
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index a80a5b5..852c9b65 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -92,7 +92,7 @@
WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.setTitle("Application Error: " + mProc.info.processName);
attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR
- | WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ | WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
if (mProc.isPersistent()) {
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index cb76e2f..65d7e01 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -94,7 +94,7 @@
WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.setTitle("Application Not Responding: " + mProc.info.processName);
attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 98f5557..bbf5772 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -78,7 +78,7 @@
WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6c4cc2d..2ac6eb0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -68,6 +68,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -1398,6 +1399,12 @@
}
private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+ PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
+ if (packageManagerInternal.getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M) {
+ return;
+ }
+
PackageManager packageManager = mContext.getPackageManager();
String[] packageNames = packageManager.getPackagesForUid(uid);
if (ArrayUtils.isEmpty(packageNames)) {
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 1a5dac5..e9d2b31 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -18,6 +18,23 @@
"include-filter": "com.android.server.appop"
}
]
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SplitPermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionFlagsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+ }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 027e2fb..0fabd9a 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.server.compat.config.Change;
import com.android.server.compat.config.XmlParser;
@@ -186,6 +187,43 @@
}
return overrideExists;
}
+ /**
+ * Overrides the enabled state for a given change and app. This method is intended to be used
+ * *only* for debugging purposes.
+ *
+ * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+ *
+ * @param overrides list of overrides to default changes config.
+ * @param packageName app for which the overrides will be applied.
+ */
+ public void addOverrides(
+ CompatibilityChangeConfig overrides, String packageName) {
+ synchronized (mChanges) {
+ for (Long changeId: overrides.enabledChanges()) {
+ addOverride(changeId, packageName, true);
+ }
+ for (Long changeId: overrides.disabledChanges()) {
+ addOverride(changeId, packageName, false);
+ }
+ }
+ }
+
+ /**
+ * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
+ * {@link #addAppOverrides(CompatibilityChangeConfig, String)} for a certain package.
+ *
+ * <p>This restores the default behaviour for the given change and app, once any app
+ * processes have been restarted.
+ *
+ * @param packageName The package for which the overrides should be purged.
+ */
+ public void removePackageOverrides(String packageName) {
+ synchronized (mChanges) {
+ for (int i = 0; i < mChanges.size(); ++i) {
+ mChanges.valueAt(i).removePackageOverride(packageName);
+ }
+ }
+ }
/**
* Dumps the current list of compatibility config information.
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index a737888..8a7dcc1 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -23,6 +23,7 @@
import android.util.StatsLog;
import com.android.internal.compat.ChangeReporter;
+import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.util.DumpUtils;
@@ -100,6 +101,17 @@
}
@Override
+ public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
+ CompatConfig.get().addOverrides(overrides, packageName);
+ }
+
+ @Override
+ public void clearOverrides(String packageName) {
+ CompatConfig config = CompatConfig.get();
+ config.removePackageOverrides(packageName);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
CompatConfig.get().dumpConfig(pw);
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 96ba8ef..648e07a 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -394,14 +394,10 @@
static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
-
// TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event.
// True by default.
static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug";
- // TODO(OEM): Set this to true to enable 'Set Menu Language' feature. False by default.
- static final String PROPERTY_SET_MENU_LANGUAGE = "ro.hdmi.set_menu_language";
-
/**
* Property to save the ARC port id on system audio device.
* <p>When ARC is initiated, this port will be used to turn on ARC.
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index b4c7dd3..080e6ce 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -333,7 +333,8 @@
current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
current.mPortId = getPortId(current.mPhysicalAddress);
current.mDeviceType = params[2] & 0xFF;
- current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType);
+ // Keep display name empty. TIF fallbacks to the service label provided by the package mg.
+ current.mDisplayName = "";
// This is to manager CEC device separately in case they don't have address.
if (mIsTvDevice) {
@@ -359,17 +360,13 @@
return;
}
- String displayName = null;
+ String displayName = "";
try {
- if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) {
- displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
- } else {
+ if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
displayName = new String(cmd.getParams(), "US-ASCII");
}
} catch (UnsupportedEncodingException e) {
Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
- // If failed to get display name, use the default name of device.
- displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
}
current.mDisplayName = displayName;
increaseProcessedDeviceCount();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 61d4d4b..dde873b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -489,7 +489,7 @@
if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
addCecDevice(new HdmiDeviceInfo(
address, path, mService.pathToPortId(path), type,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
+ Constants.UNKNOWN_VENDOR_ID, ""));
// if we are adding a new device info, send out a give osd name command
// to update the name of the device in TIF
mService.sendCecCommand(
@@ -526,7 +526,8 @@
return true;
}
- if (deviceInfo.getDisplayName().equals(osdName)) {
+ if (deviceInfo.getDisplayName() != null
+ && deviceInfo.getDisplayName().equals(osdName)) {
Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
return true;
}
@@ -1238,8 +1239,8 @@
}
// Wake up if the current device if ready to route.
mService.wakeUp();
- if (getLocalActivePort() == portId) {
- HdmiLogger.debug("Not switching to the same port " + portId);
+ if ((getLocalActivePort() == portId) && (portId != Constants.CEC_SWITCH_ARC)) {
+ HdmiLogger.debug("Not switching to the same port " + portId + " except for arc");
return;
}
// Switch to HOME if the current active port is not HOME yet
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 413e7a0..0944324 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -24,6 +24,7 @@
import android.os.PowerManager.WakeLock;
import android.os.SystemProperties;
import android.provider.Settings.Global;
+import android.sysprop.HdmiProperties;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -47,7 +48,7 @@
SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true);
private static final boolean SET_MENU_LANGUAGE =
- SystemProperties.getBoolean(Constants.PROPERTY_SET_MENU_LANGUAGE, false);
+ HdmiProperties.set_menu_language().orElse(false);
// Used to keep the device awake while it is the active source. For devices that
// cannot wake up via CEC commands, this address the inconvenience of having to
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7e6e668..362955d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -71,7 +71,6 @@
import android.view.IInputFilter;
import android.view.IInputFilterHost;
import android.view.IInputMonitorHost;
-import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -186,9 +185,6 @@
IInputFilter mInputFilter; // guarded by mInputFilterLock
InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
- private IWindow mFocusedWindow;
- private boolean mFocusedWindowHasCapture;
-
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);
@@ -203,8 +199,7 @@
int deviceId, int sourceMask, int sw);
private static native boolean nativeHasKeys(long ptr,
int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
- private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
- int displayId);
+ private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel);
private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel,
int displayId, boolean isGestureMonitor);
private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
@@ -529,7 +524,6 @@
throw new IllegalArgumentException("displayId must >= 0.");
}
-
final long ident = Binder.clearCallingIdentity();
try {
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
@@ -537,29 +531,25 @@
inputChannels[0].setToken(host.asBinder());
nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId,
true /*isGestureMonitor*/);
- return new InputMonitor(inputChannelName, inputChannels[1], host);
+ return new InputMonitor(inputChannels[1], host);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
- * Registers an input channel so that it can be used as an input event target.
+ * Registers an input channel so that it can be used as an input event target. The channel is
+ * registered with a generated token.
+ *
* @param inputChannel The input channel to register.
- * @param inputWindowHandle The handle of the input window associated with the
- * input channel, or null if none.
*/
- public void registerInputChannel(InputChannel inputChannel, IBinder token) {
+ public void registerInputChannel(InputChannel inputChannel) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null.");
}
+ inputChannel.setToken(new Binder());
- if (token == null) {
- token = new Binder();
- }
- inputChannel.setToken(token);
-
- nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
+ nativeRegisterInputChannel(mPtr, inputChannel);
}
/**
@@ -1513,26 +1503,9 @@
@Override
public void requestPointerCapture(IBinder windowToken, boolean enabled) {
- if (mFocusedWindow == null || mFocusedWindow.asBinder() != windowToken) {
- Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
- + windowToken);
- return;
- }
- if (mFocusedWindowHasCapture == enabled) {
- Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
- return;
- }
- setPointerCapture(enabled);
- }
-
- private void setPointerCapture(boolean enabled) {
- if (mFocusedWindowHasCapture != enabled) {
- mFocusedWindowHasCapture = enabled;
- try {
- mFocusedWindow.dispatchPointerCaptureChanged(enabled);
- } catch (RemoteException ex) {
- /* ignore */
- }
+ boolean requestConfigurationRefresh =
+ mWindowManagerCallbacks.requestPointerCapture(windowToken, enabled);
+ if (requestConfigurationRefresh) {
nativeSetPointerCapture(mPtr, enabled);
}
}
@@ -1829,16 +1802,11 @@
// Native callback
private void notifyFocusChanged(IBinder oldToken, IBinder newToken) {
- if (mFocusedWindow != null) {
- if (mFocusedWindow.asBinder() == newToken) {
- Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
- + mFocusedWindow);
- return;
- }
- setPointerCapture(false);
+ final boolean requestConfigurationRefresh =
+ mWindowManagerCallbacks.notifyFocusChanged(oldToken, newToken);
+ if (requestConfigurationRefresh) {
+ nativeSetPointerCapture(mPtr, false);
}
-
- mFocusedWindow = IWindow.Stub.asInterface(newToken);
}
// Native callback.
@@ -2116,6 +2084,20 @@
* @param touchedToken The token for the window that received the input event.
*/
void onPointerDownOutsideFocus(IBinder touchedToken);
+
+ /**
+ * Called when the focused window has changed.
+ *
+ * @return true if we want to request a configuration refresh.
+ */
+ boolean notifyFocusChanged(IBinder oldToken, IBinder newToken);
+
+ /**
+ * Called by the client to request pointer capture.
+ *
+ * @return true if we want to request a configuration refresh.
+ */
+ boolean requestPointerCapture(IBinder windowToken, boolean enabled);
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b7fcd3f..471fa72 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3570,10 +3570,14 @@
return;
}
if (!setVisible) {
- // Client hides the IME directly.
- if (mCurClient != null && mCurClient.client != null) {
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
- MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, mCurClient));
+ if (mCurClient != null) {
+ // IMMS only knows of focused window, not the actual IME target.
+ // e.g. it isn't aware of any window that has both
+ // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
+ // Send it to window manager to hide IME from IME target window.
+ // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
+ // actual IME target.
+ mWindowManagerInternal.hideIme(mCurClient.selfReportedDisplayId);
}
} else {
// Send to window manager to show IME after IME layout finishes.
@@ -4208,7 +4212,7 @@
// with other IME windows based on type vs. grouping based on whichever token happens
// to get selected by the system later on.
attrs.token = mSwitchingDialogToken;
- attrs.privateFlags |= LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ attrs.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
index 4ed581d..6416505 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
@@ -37,6 +37,9 @@
/**
* Match the list of rules against an app install metadata.
*
+ * <p>Rules must be in disjunctive normal form (DNF). A rule should contain AND'ed formulas
+ * only. All rules are OR'ed together by default.
+ *
* @param rules The list of rules to evaluate.
* @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
* against.
@@ -45,7 +48,7 @@
*/
static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) {
for (Rule rule : rules) {
- if (isMatch(rule, appInstallMetadata)) {
+ if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) {
return rule;
}
}
@@ -88,11 +91,8 @@
// NOT connector has only 1 formula attached.
return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata);
case AND:
- boolean result = true;
- for (Formula subFormula : openFormula.getFormulas()) {
- result &= isMatch(subFormula, appInstallMetadata);
- }
- return result;
+ return openFormula.getFormulas().stream().allMatch(
+ subFormula -> isMatch(subFormula, appInstallMetadata));
default:
Slog.i(TAG, String.format("Returned no match for unknown connector %s",
openFormula.getConnector()));
@@ -102,4 +102,25 @@
return false;
}
+
+ private static boolean isConjunctionOfFormulas(Formula formula) {
+ if (formula == null) {
+ return false;
+ }
+ if (isAtomicFormula(formula)) {
+ return true;
+ }
+ OpenFormula openFormula = (OpenFormula) formula;
+ return openFormula.getConnector() == OpenFormula.Connector.AND
+ && openFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula);
+ }
+
+ private static boolean isAtomicFormula(Formula formula) {
+ if (formula instanceof AtomicFormula) {
+ return true;
+ }
+ OpenFormula openFormula = (OpenFormula) formula;
+ return openFormula.getConnector() == OpenFormula.Connector.NOT
+ && openFormula.getFormulas().get(0) instanceof AtomicFormula;
+ }
}
diff --git a/services/core/java/com/android/server/integrity/model/AtomicFormula.java b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
index dde8c2a..b9b46e3 100644
--- a/services/core/java/com/android/server/integrity/model/AtomicFormula.java
+++ b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
@@ -22,6 +22,8 @@
import android.annotation.Nullable;
import android.util.Slog;
+import java.util.Objects;
+
/**
* Represents a simple formula consisting of an app install metadata field and a value.
*
@@ -130,11 +132,6 @@
return mBoolValue.toString();
}
- @Override
- public String toString() {
- return String.format("%s %s %s", mKey, mOperator, getValue());
- }
-
/**
* Check if the formula is true when substituting its {@link Key} with the string value.
*
@@ -188,6 +185,32 @@
return false;
}
+ @Override
+ public String toString() {
+ return String.format("%s %s %s", mKey, mOperator, getValue());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AtomicFormula that = (AtomicFormula) o;
+ return mKey == that.mKey
+ && mOperator == that.mOperator
+ && Objects.equals(mStringValue, that.mStringValue)
+ && Objects.equals(mIntValue, that.mIntValue)
+ && Objects.equals(mBoolValue, that.mBoolValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKey, mOperator, mStringValue, mIntValue, mBoolValue);
+ }
+
private void validateOperator(Key key, Operator operator) {
boolean validOperator;
switch (key) {
diff --git a/services/core/java/com/android/server/integrity/model/OpenFormula.java b/services/core/java/com/android/server/integrity/model/OpenFormula.java
index 5ba9f46..21da629 100644
--- a/services/core/java/com/android/server/integrity/model/OpenFormula.java
+++ b/services/core/java/com/android/server/integrity/model/OpenFormula.java
@@ -21,6 +21,7 @@
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Represents a complex formula consisting of other simple and complex formulas.
@@ -64,6 +65,24 @@
return sb.toString();
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OpenFormula that = (OpenFormula) o;
+ return mConnector == that.mConnector
+ && mFormulas.equals(that.mFormulas);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConnector, mFormulas);
+ }
+
private void validateFormulas(Connector connector, List<Formula> formulas) {
switch (connector) {
case AND:
diff --git a/services/core/java/com/android/server/integrity/model/Rule.java b/services/core/java/com/android/server/integrity/model/Rule.java
index 41611d0..63b9b91 100644
--- a/services/core/java/com/android/server/integrity/model/Rule.java
+++ b/services/core/java/com/android/server/integrity/model/Rule.java
@@ -18,6 +18,8 @@
import static com.android.internal.util.Preconditions.checkNotNull;
+import java.util.Objects;
+
/**
* Represent rules to be used in the rule evaluation engine to match against app installs.
*
@@ -66,4 +68,22 @@
public String toString() {
return String.format("Rule: %s, %s", mFormula, mEffect);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Rule that = (Rule) o;
+ return Objects.equals(mFormula, that.mFormula)
+ && Objects.equals(mEffect, that.mEffect);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFormula, mEffect);
+ }
}
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index da31d0b..61e5d1f 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -16,6 +16,8 @@
package com.android.server.location;
+import android.annotation.NonNull;
+
/**
* Represents the calling process's uid, pid, and package name.
*/
@@ -23,10 +25,13 @@
public final int mUid;
public final int mPid;
public final String mPackageName;
+ public final @NonNull String mListenerIdentifier;
- public CallerIdentity(int uid, int pid, String packageName) {
+ public CallerIdentity(int uid, int pid, String packageName,
+ @NonNull String listenerIdentifier) {
mUid = uid;
mPid = pid;
mPackageName = packageName;
+ mListenerIdentifier = listenerIdentifier;
}
}
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index a192206..895c4b3 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -16,6 +16,7 @@
package com.android.server.location;
+import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -151,14 +152,16 @@
}
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
- int allowedResolutionLevel, int uid, String packageName) {
+ int allowedResolutionLevel, int uid, String packageName,
+ @NonNull String listenerIdentifier) {
if (D) {
Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
+ ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName);
}
GeofenceState state = new GeofenceState(geofence,
- request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent);
+ request.getExpireAt(), allowedResolutionLevel, uid, packageName, listenerIdentifier,
+ intent);
synchronized (mLock) {
// first make sure it doesn't already exist
for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -301,7 +304,8 @@
int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
if (op >= 0) {
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
- state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
+ state.mPackageName, state.mListenerIdentifier)
+ != AppOpsManager.MODE_ALLOWED) {
if (D) {
Slog.d(TAG, "skipping geofence processing for no op app: "
+ state.mPackageName);
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
index 3ebe20a..fe0719d 100644
--- a/services/core/java/com/android/server/location/GeofenceState.java
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -17,6 +17,7 @@
package com.android.server.location;
+import android.annotation.NonNull;
import android.app.PendingIntent;
import android.location.Geofence;
import android.location.Location;
@@ -38,13 +39,14 @@
public final int mAllowedResolutionLevel;
public final int mUid;
public final String mPackageName;
+ public final @NonNull String mListenerIdentifier;
public final PendingIntent mIntent;
int mState; // current state
double mDistanceToCenter; // current distance to center of fence
- public GeofenceState(Geofence fence, long expireAt,
- int allowedResolutionLevel, int uid, String packageName, PendingIntent intent) {
+ public GeofenceState(Geofence fence, long expireAt, int allowedResolutionLevel, int uid,
+ String packageName, @NonNull String listenerIdentifier, PendingIntent intent) {
mState = STATE_UNKNOWN;
mDistanceToCenter = Double.MAX_VALUE;
@@ -53,6 +55,7 @@
mAllowedResolutionLevel = allowedResolutionLevel;
mUid = uid;
mPackageName = packageName;
+ mListenerIdentifier = listenerIdentifier;
mIntent = intent;
mLocation = new Location("");
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index aa8a25a..0929d93 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -182,7 +182,9 @@
}
return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
- callerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
+ callerIdentity.mPackageName,
+ "Location sent to " + callerIdentity.mListenerIdentifier)
+ == AppOpsManager.MODE_ALLOWED;
}
protected void logPermissionDisabledEventNotReported(String tag, String packageName,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index bad484f..34fb641 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -118,6 +118,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.CredentialType;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -2058,7 +2059,8 @@
@UserIdInt int userHandle) {
synchronized (this) {
mUserPasswordMetrics.put(userHandle,
- PasswordMetrics.computeForCredential(credentialType, password));
+ PasswordMetrics.computeForCredential(
+ LockscreenCredential.createRaw(credentialType, password)));
}
}
@@ -2069,7 +2071,7 @@
// since the user never unlock the device manually. In this case, always
// return a default metrics object. This is to distinguish this case from
// the case where during boot user password is unknown yet (returning null here)
- return new PasswordMetrics();
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
}
synchronized (this) {
return mUserPasswordMetrics.get(userHandle);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index a5d59e3..0a8e5bd 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -16,16 +16,12 @@
package com.android.server.locksettings;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
import android.app.ActivityManager;
import android.os.ShellCommand;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+import com.android.internal.widget.LockscreenCredential;
import java.io.PrintWriter;
@@ -189,31 +185,49 @@
mLockPatternUtils.isSyntheticPasswordEnabled()));
}
+ private LockscreenCredential getOldCredential() {
+ if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) {
+ final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId);
+ if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
+ return LockscreenCredential.createPassword(mOld);
+ } else {
+ return LockscreenCredential.createPin(mOld);
+ }
+ } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
+ return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ mOld.getBytes()));
+ } else {
+ return LockscreenCredential.createNone();
+ }
+ }
+
private void runSetPattern() {
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.saveLockPattern(stringToPattern(mNew), oldBytes, mCurrentUserId);
+ mLockPatternUtils.setLockCredential(
+ LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ mNew.getBytes())),
+ getOldCredential(),
+ mCurrentUserId);
getOutPrintWriter().println("Pattern set to '" + mNew + "'");
}
private void runSetPassword() {
- byte[] newBytes = mNew != null ? mNew.getBytes() : null;
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_ALPHABETIC,
+ mLockPatternUtils.setLockCredential(LockscreenCredential.createPassword(mNew),
+ getOldCredential(),
mCurrentUserId);
getOutPrintWriter().println("Password set to '" + mNew + "'");
}
private void runSetPin() {
- byte[] newBytes = mNew != null ? mNew.getBytes() : null;
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_NUMERIC,
+ mLockPatternUtils.setLockCredential(LockscreenCredential.createPin(mNew),
+ getOldCredential(),
mCurrentUserId);
getOutPrintWriter().println("Pin set to '" + mNew + "'");
}
private void runClear() {
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.clearLock(oldBytes, mCurrentUserId);
+ mLockPatternUtils.setLockCredential(LockscreenCredential.createNone(),
+ getOldCredential(),
+ mCurrentUserId);
getOutPrintWriter().println("Lock credential cleared");
}
@@ -238,13 +252,8 @@
}
try {
- final boolean result;
- if (havePassword) {
- byte[] passwordBytes = mOld != null ? mOld.getBytes() : null;
- result = mLockPatternUtils.checkPassword(passwordBytes, mCurrentUserId);
- } else {
- result = mLockPatternUtils.checkPattern(stringToPattern(mOld), mCurrentUserId);
- }
+ final boolean result = mLockPatternUtils.checkCredential(getOldCredential(),
+ mCurrentUserId, null);
if (!result) {
if (!mLockPatternUtils.isManagedProfileWithUnifiedChallenge(mCurrentUserId)) {
mLockPatternUtils.reportFailedPasswordAttempt(mCurrentUserId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 5594614..f4cad63 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -95,8 +95,6 @@
@VisibleForTesting
public static class CredentialHash {
- /** Deprecated private static final int VERSION_LEGACY = 0; */
- private static final int VERSION_GATEKEEPER = 1;
private CredentialHash(byte[] hash, @CredentialType int type) {
if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
@@ -126,42 +124,6 @@
byte[] hash;
@CredentialType int type;
-
- public byte[] toBytes() {
- try {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(os);
- dos.write(VERSION_GATEKEEPER);
- dos.write(type);
- if (hash != null && hash.length > 0) {
- dos.writeInt(hash.length);
- dos.write(hash);
- } else {
- dos.writeInt(0);
- }
- dos.close();
- return os.toByteArray();
- } catch (IOException e) {
- throw new IllegalStateException("Fail to serialze credential hash", e);
- }
- }
-
- public static CredentialHash fromBytes(byte[] bytes) {
- try {
- DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
- /* int version = */ is.read();
- int type = is.read();
- int hashSize = is.readInt();
- byte[] hash = null;
- if (hashSize > 0) {
- hash = new byte[hashSize];
- is.readFully(hash);
- }
- return new CredentialHash(hash, type);
- } catch (IOException e) {
- throw new IllegalStateException("Fail to deserialze credential hash", e);
- }
- }
}
public LockSettingsStorage(Context context) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index e753a7b..9eac252 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -93,7 +93,7 @@
public void unselectRoute(String packageName, String routeId) {
if (mConnectionReady) {
- mActiveConnection.unselectRotue(packageName, routeId);
+ mActiveConnection.unselectRoute(packageName, routeId);
updateBinding();
}
}
@@ -105,6 +105,20 @@
}
}
+ public void requestSetVolume(MediaRoute2Info route, int volume) {
+ if (mConnectionReady) {
+ mActiveConnection.requestSetVolume(route.getId(), volume);
+ updateBinding();
+ }
+ }
+
+ public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ if (mConnectionReady) {
+ mActiveConnection.requestUpdateVolume(route.getId(), delta);
+ updateBinding();
+ }
+ }
+
@Nullable
public MediaRoute2ProviderInfo getProviderInfo() {
return mProviderInfo;
@@ -324,7 +338,7 @@
}
}
- public void unselectRotue(String packageName, String routeId) {
+ public void unselectRoute(String packageName, String routeId) {
try {
mProvider.unselectRoute(packageName, routeId);
} catch (RemoteException ex) {
@@ -340,6 +354,22 @@
}
}
+ public void requestSetVolume(String routeId, int volume) {
+ try {
+ mProvider.requestSetVolume(routeId, volume);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to request set volume.", ex);
+ }
+ }
+
+ public void requestUpdateVolume(String routeId, int delta) {
+ try {
+ mProvider.requestUpdateVolume(routeId, delta);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to request update volume.", ex);
+ }
+ }
+
@Override
public void binderDied() {
mHandler.post(() -> onConnectionDied(Connection.this));
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 668f2be..74d59ac 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -198,6 +198,34 @@
}
}
+ public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestSetVolumeLocked(client, route, volume);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestUpdateVolumeLocked(client, route, delta);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void selectClientRoute2(@NonNull IMediaRouter2Manager manager,
String packageName, @Nullable MediaRoute2Info route) {
final long token = Binder.clearCallingIdentity();
@@ -210,6 +238,37 @@
}
}
+ public void requestSetVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(manager, "manager must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestSetVolumeLocked(manager, route, volume);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(manager, "manager must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestUpdateVolumeLocked(manager, route, delta);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+
public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) {
Objects.requireNonNull(client, "client must not be null");
@@ -362,6 +421,30 @@
}
}
+ private void requestSetVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
+ int volume) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestSetVolume,
+ clientRecord.mUserRecord.mHandler, route, volume));
+ }
+ }
+
+ private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
+ int delta) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestUpdateVolume,
+ clientRecord.mUserRecord.mHandler, route, delta));
+ }
+ }
+
private void registerManagerLocked(IMediaRouter2Manager manager,
int uid, int pid, String packageName, int userId, boolean trusted) {
final IBinder binder = manager.asBinder();
@@ -424,6 +507,31 @@
}
}
+ private void requestSetVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
+ int volume) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord != null) {
+ managerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestSetVolume,
+ managerRecord.mUserRecord.mHandler, route, volume));
+ }
+ }
+
+ private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
+ int delta) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord != null) {
+ managerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestUpdateVolume,
+ managerRecord.mUserRecord.mHandler, route, delta));
+ }
+ }
+
+
private void initializeUserLocked(UserRecord userRecord) {
if (DEBUG) {
Slog.d(TAG, userRecord + ": Initialized");
@@ -679,6 +787,20 @@
}
}
+ private void requestSetVolume(MediaRoute2Info route, int volume) {
+ final MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
+ if (provider != null) {
+ provider.requestSetVolume(route, volume);
+ }
+ }
+
+ private void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ final MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
+ if (provider != null) {
+ provider.requestUpdateVolume(route, delta);
+ }
+ }
+
private void scheduleUpdateProviderInfos() {
if (!mProviderInfosUpdateScheduled) {
mProviderInfosUpdateScheduled = true;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 796a25d..afd92f6 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -499,6 +499,32 @@
mService2.setControlCategories2(client, categories);
}
+ // Binder call
+ @Override
+ public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
+ mService2.requestSetVolume2(client, route, volume);
+ }
+
+ // Binder call
+ @Override
+ public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
+ mService2.requestUpdateVolume2(client, route, delta);
+ }
+
+ // Binder call
+ @Override
+ public void requestSetVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int volume) {
+ mService2.requestSetVolume2Manager(manager, route, volume);
+ }
+
+ // Binder call
+ @Override
+ public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int delta) {
+ mService2.requestUpdateVolume2Manager(manager, route, delta);
+ }
+
void restoreBluetoothA2dp() {
try {
boolean a2dpOn;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 976a0c6..812ce32 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -217,7 +217,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.SomeArgs;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -385,6 +384,7 @@
private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
+ private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -1509,6 +1509,11 @@
latch.await(5, TimeUnit.SECONDS);
}
+ @VisibleForTesting
+ Handler getHandlerForTesting() {
+ return mHandler;
+ }
+
/**
* Update mobile policies with data cycle information from {@link CarrierConfigManager}
* if necessary.
@@ -3064,6 +3069,34 @@
mContext.enforceCallingOrSelfPermission(MANAGE_SUBSCRIPTION_PLANS, TAG);
}
+ private void enforceSubscriptionPlanValidity(SubscriptionPlan[] plans) {
+ // nothing to check if no plans
+ if (plans.length == 0) {
+ return;
+ }
+
+ long applicableNetworkTypes = 0;
+ boolean allNetworks = false;
+ for (SubscriptionPlan plan : plans) {
+ if (plan.getNetworkTypes() == null) {
+ allNetworks = true;
+ } else {
+ if ((applicableNetworkTypes & plan.getNetworkTypesBitMask()) != 0) {
+ throw new IllegalArgumentException(
+ "Multiple subscription plans defined for a single network type.");
+ } else {
+ applicableNetworkTypes |= plan.getNetworkTypesBitMask();
+ }
+ }
+ }
+
+ // ensure at least one plan applies for every network type
+ if (!allNetworks) {
+ throw new IllegalArgumentException(
+ "No generic subscription plan that applies to all network types.");
+ }
+ }
+
@Override
public SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
@@ -3228,6 +3261,7 @@
@Override
public void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
+ enforceSubscriptionPlanValidity(plans);
for (SubscriptionPlan plan : plans) {
Preconditions.checkNotNull(plan);
@@ -3256,6 +3290,8 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
mContext.sendBroadcast(intent, android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_SUBSCRIPTION_PLANS_CHANGED, subId, 0, plans));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3282,7 +3318,7 @@
@Override
public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue,
- long networkTypeMask, long timeoutMillis, String callingPackage) {
+ long timeoutMillis, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
// We can only override when carrier told us about plans
@@ -3300,16 +3336,11 @@
final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(),
NETPOLICY_OVERRIDE_ENABLED, 1) != 0;
if (overrideEnabled || overrideValue == 0) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = subId;
- args.arg2 = overrideMask;
- args.arg3 = overrideValue;
- args.arg4 = networkTypeMask;
- mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+ overrideMask, overrideValue, subId));
if (timeoutMillis > 0) {
- args.arg3 = 0;
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args),
- timeoutMillis);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+ overrideMask, 0, subId), timeoutMillis);
}
}
}
@@ -4445,11 +4476,20 @@
}
private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
- int overrideMask, int overrideValue, long networkTypeMask) {
+ int overrideMask, int overrideValue) {
if (listener != null) {
try {
- listener.onSubscriptionOverride(subId, overrideMask, overrideValue,
- networkTypeMask);
+ listener.onSubscriptionOverride(subId, overrideMask, overrideValue);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ private void dispatchSubscriptionPlansChanged(INetworkPolicyListener listener, int subId,
+ SubscriptionPlan[] plans) {
+ if (listener != null) {
+ try {
+ listener.onSubscriptionPlansChanged(subId, plans);
} catch (RemoteException ignored) {
}
}
@@ -4550,16 +4590,13 @@
return true;
}
case MSG_SUBSCRIPTION_OVERRIDE: {
- final SomeArgs args = (SomeArgs) msg.obj;
- final int subId = (int) args.arg1;
- final int overrideMask = (int) args.arg2;
- final int overrideValue = (int) args.arg3;
- final long networkTypeMask = (long) args.arg4;
+ final int overrideMask = msg.arg1;
+ final int overrideValue = msg.arg2;
+ final int subId = (int) msg.obj;
final int length = mListeners.beginBroadcast();
for (int i = 0; i < length; i++) {
final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
- dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue,
- networkTypeMask);
+ dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue);
}
mListeners.finishBroadcast();
return true;
@@ -4576,6 +4613,17 @@
setNetworkTemplateEnabledInner(template, enabled);
return true;
}
+ case MSG_SUBSCRIPTION_PLANS_CHANGED: {
+ final SubscriptionPlan[] plans = (SubscriptionPlan[]) msg.obj;
+ final int subId = msg.arg1;
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ dispatchSubscriptionPlansChanged(listener, subId, plans);
+ }
+ mListeners.finishBroadcast();
+ return true;
+ }
default: {
return false;
}
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 61be1f5..6f0ad33 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import android.app.Notification;
+import android.net.Uri;
import android.service.notification.NotificationStats;
import com.android.internal.statusbar.NotificationVisibility;
@@ -49,6 +50,12 @@
void onNotificationBubbleChanged(String key, boolean isBubble);
/**
+ * Grant permission to read the specified URI to the package associated with the
+ * NotificationRecord associated with the given key.
+ */
+ void grantInlineReplyUriPermission(String key, Uri uri, int callingUid);
+
+ /**
* Notifies that smart replies and actions have been added to the UI.
*/
void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4457e9c..cd3343b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -21,6 +21,7 @@
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
@@ -55,6 +56,7 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.UserHandle.USER_NULL;
@@ -1154,6 +1156,56 @@
}
}
}
+
+ @Override
+ /**
+ * Grant permission to read the specified URI to the package specified in the
+ * NotificationRecord associated with the given key. The callingUid represents the UID of
+ * SystemUI from which this method is being called.
+ *
+ * For this to work, SystemUI must have permission to read the URI when running under the
+ * user associated with the NotificationRecord, and this grant will fail when trying
+ * to grant URI permissions across users.
+ */
+ public void grantInlineReplyUriPermission(String key, Uri uri, int callingUid) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ IBinder owner = r.permissionOwner;
+ if (owner == null) {
+ r.permissionOwner = mUgmInternal.newUriPermissionOwner("NOTIF:" + key);
+ owner = r.permissionOwner;
+ }
+ int uid = callingUid;
+ int userId = r.sbn.getUserId();
+ if (userId == UserHandle.USER_ALL) {
+ userId = USER_SYSTEM;
+ }
+ if (UserHandle.getUserId(uid) != userId) {
+ try {
+ final String[] pkgs = mPackageManager.getPackagesForUid(callingUid);
+ if (pkgs == null) {
+ Log.e(TAG, "Cannot grant uri permission to unknown UID: "
+ + callingUid);
+ }
+ final String pkg = pkgs[0]; // Get the SystemUI package
+ // Find the UID for SystemUI for the correct user
+ uid = mPackageManager.getPackageUid(pkg, 0, userId);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Cannot talk to package manager", re);
+ }
+ }
+ grantUriPermission(owner, uri, uid, r.sbn.getPackageName(), userId);
+ } else {
+ Log.w(TAG, "No record found for notification key:" + key);
+
+ // TODO: figure out cancel story. I think it's: sysui needs to tell us
+ // whenever noitifications held by a lifetimextender go away
+ // IBinder owner = mUgmInternal.newUriPermissionOwner("InlineReply:" + key);
+ // pass in userId and package as well as key (key for logging purposes)
+ }
+ }
+ }
};
@VisibleForTesting
@@ -1183,7 +1235,7 @@
}
@GuardedBy("mNotificationLock")
- private void clearSoundLocked() {
+ void clearSoundLocked() {
mSoundNotificationKey = null;
long identity = Binder.clearCallingIdentity();
try {
@@ -1198,7 +1250,7 @@
}
@GuardedBy("mNotificationLock")
- private void clearVibrateLocked() {
+ void clearVibrateLocked() {
mVibrateNotificationKey = null;
long identity = Binder.clearCallingIdentity();
try {
@@ -4495,13 +4547,13 @@
if (record != null && record.getAudioAttributes() != null) {
if ((mListenerHints & HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0) {
if (record.getAudioAttributes().getUsage()
- != AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+ != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
return "listenerNoti";
}
}
if ((mListenerHints & HINT_HOST_DISABLE_CALL_EFFECTS) != 0) {
if (record.getAudioAttributes().getUsage()
- == AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+ == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
return "listenerCall";
}
}
@@ -5060,8 +5112,8 @@
}
if (contentViewSize >= mStripRemoteViewsSizeBytes) {
mUsageStats.registerImageRemoved(pkg);
- Slog.w(TAG,
- "Removed too large RemoteViews on pkg: " + pkg + " tag: " + tag + " id: " + id);
+ Slog.w(TAG, "Removed too large RemoteViews (" + contentViewSize + " bytes) on pkg: "
+ + pkg + " tag: " + tag + " id: " + id);
return true;
}
return false;
@@ -6060,7 +6112,6 @@
mIsAutomotive
? record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
: record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
-
// Remember if this notification already owns the notification channels.
boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
@@ -6076,7 +6127,6 @@
}
if (aboveThreshold && isNotificationForCurrentUser(record)) {
-
if (mSystemReady && mAudioManager != null) {
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
@@ -6091,7 +6141,6 @@
vibration = mFallbackVibrationPattern;
}
hasValidVibrate = vibration != null;
-
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
if (!sentAccessibilityEvent) {
@@ -6248,11 +6297,29 @@
return true;
}
+ // A looping ringtone, such as an incoming call is playing
+ if (isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
+ || isLoopingRingtoneNotification(
+ mNotificationsByKey.get(mVibrateNotificationKey))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @GuardedBy("mNotificationLock")
+ private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
+ if (playingRecord != null) {
+ if (playingRecord.getAudioAttributes().getUsage() == USAGE_NOTIFICATION_RINGTONE
+ && (playingRecord.getNotification().flags & FLAG_INSISTENT) != 0) {
+ return true;
+ }
+ }
return false;
}
private boolean playSound(final NotificationRecord record, Uri soundUri) {
- boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+ boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0;
// play notifications if there is no user of exclusive audio focus
// and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
// VIBRATE ringer mode)
@@ -6304,7 +6371,6 @@
try {
Thread.sleep(waitMs);
} catch (InterruptedException e) { }
-
// Notifications might be canceled before it actually vibrates due to waitMs,
// so need to check the notification still valide for vibrate.
synchronized (mNotificationLock) {
@@ -7012,7 +7078,6 @@
private void grantUriPermission(IBinder owner, Uri uri, int sourceUid, String targetPkg,
int targetUserId) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
-
final long ident = Binder.clearCallingIdentity();
try {
mUgm.grantUriPermissionFromOwner(owner, sourceUid, targetPkg,
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 9e7b4648..5f3e503 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -17,11 +17,13 @@
package com.android.server.om;
import static android.app.AppGlobals.getPackageManager;
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_REASON;
import static android.content.pm.PackageManager.SIGNATURE_MATCH;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
@@ -356,7 +358,11 @@
}
break;
case ACTION_PACKAGE_CHANGED:
- onPackageChanged(packageName, userIds);
+ // ignore the intent if it was sent by the package manager as a result of the
+ // overlay manager having sent ACTION_OVERLAY_CHANGED
+ if (!ACTION_OVERLAY_CHANGED.equals(intent.getStringExtra(EXTRA_REASON))) {
+ onPackageChanged(packageName, userIds);
+ }
break;
case ACTION_PACKAGE_REMOVED:
if (replacing) {
@@ -885,7 +891,7 @@
FgThread.getHandler().post(() -> {
updateAssets(userId, targetPackageName);
- final Intent intent = new Intent(Intent.ACTION_OVERLAY_CHANGED,
+ final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", targetPackageName, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
diff --git a/services/core/java/com/android/server/os/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING
new file mode 100644
index 0000000..502f1e8
--- /dev/null
+++ b/services/core/java/com/android/server/os/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsUsbTests"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 05b6168..c8179a7 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -131,7 +131,7 @@
private static class FeatureConfigImpl implements FeatureConfig {
private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
- private volatile boolean mFeatureEnabled = true;
+ private volatile boolean mFeatureEnabled = false;
private CompatConfig mCompatibility;
private FeatureConfigImpl(PackageManagerService.Injector injector) {
@@ -141,12 +141,12 @@
@Override
public void onSystemReady() {
mFeatureEnabled = DeviceConfig.getBoolean(
- NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME, true);
+ NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME, false);
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
properties -> {
synchronized (FeatureConfigImpl.this) {
- mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, true);
+ mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, false);
}
});
}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 976cdfb..b1eb7e7 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -50,6 +50,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.server.IntentResolver;
import java.io.PrintWriter;
@@ -386,8 +387,11 @@
addProvidersLocked(pkg, chatty);
addServicesLocked(pkg, chatty);
}
- final String setupWizardPackage = sPackageManagerInternal.getKnownPackageName(
- PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM);
+ // expect single setupwizard package
+ final String setupWizardPackage = ArrayUtils.firstOrNull(
+ sPackageManagerInternal.getKnownPackageNames(
+ PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
+
for (int i = newIntents.size() - 1; i >= 0; --i) {
final PackageParser.ActivityIntentInfo intentInfo = newIntents.get(i);
final PackageParser.Package disabledPkg = sPackageManagerInternal
@@ -421,8 +425,11 @@
final List<ActivityIntentInfo> protectedFilters = mProtectedFilters;
mProtectedFilters = null;
- final String setupWizardPackage = sPackageManagerInternal.getKnownPackageName(
- PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM);
+ // expect single setupwizard package
+ final String setupWizardPackage = ArrayUtils.firstOrNull(
+ sPackageManagerInternal.getKnownPackageNames(
+ PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
+
if (DEBUG_FILTERS && setupWizardPackage == null) {
Slog.i(TAG, "No setup wizard;"
+ " All protected intents capped to priority 0");
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
new file mode 100644
index 0000000..b0cf525
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * Immutable class holding information about where the request to install or update an app
+ * came from.
+ */
+final class InstallSource {
+ private static final InstallSource EMPTY = new InstallSource(null);
+
+ /**
+ * The package that requested the installation, if known.
+ */
+ @Nullable
+ final String initiatingPackageName;
+
+ static InstallSource create(@Nullable String initiatingPackageName) {
+ return initiatingPackageName == null
+ ? EMPTY : new InstallSource(initiatingPackageName.intern());
+ }
+
+ private InstallSource(@Nullable String initiatingPackageName) {
+ this.initiatingPackageName = initiatingPackageName;
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.printPair("installInitiatingPackageName", initiatingPackageName);
+ }
+
+ /**
+ * Return an InstallSource the same as this one except it does not refer to the specified
+ * installer package name.
+ */
+ InstallSource removeInstallerPackage(String packageName) {
+ if (packageName != null && packageName.equals(initiatingPackageName)) {
+ return create(null);
+ }
+ return this;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a7d4237..eca93bb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -482,15 +482,24 @@
throw new SecurityException("User restriction prevents installing");
}
+ String requestedInstallerPackageName = params.installerPackageName != null
+ ? params.installerPackageName : installerPackageName;
+
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
+ if (callingUid != Process.SYSTEM_UID) {
+ // The supplied installerPackageName must always belong to the calling app.
+ mAppOps.checkPackage(callingUid, installerPackageName);
+ }
// Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
// caller.
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
- PackageManager.PERMISSION_GRANTED) {
- mAppOps.checkPackage(callingUid, installerPackageName);
+ if (!requestedInstallerPackageName.equals(installerPackageName)) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
+ }
}
params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
@@ -614,11 +623,12 @@
stageCid = buildExternalStageCid(sessionId);
}
}
+ InstallSource installSource = InstallSource.create(installerPackageName);
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
- installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
- false, false, null, SessionInfo.INVALID_ID, false, false, false,
- SessionInfo.STAGED_SESSION_NO_ERROR, "");
+ requestedInstallerPackageName, callingUid, installSource, params, createdMillis,
+ stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
+ false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index b720290..d8bfa7d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -146,6 +146,8 @@
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
private static final String ATTR_INSTALLER_UID = "installerUid";
+ private static final String ATTR_INITIATING_PACKAGE_NAME =
+ "installInitiatingPackageName";
private static final String ATTR_CREATED_MILLIS = "createdMillis";
private static final String ATTR_UPDATED_MILLIS = "updatedMillis";
private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
@@ -218,6 +220,10 @@
@GuardedBy("mLock")
private int mInstallerUid;
+ /** Where this install request came from */
+ @GuardedBy("mLock")
+ private InstallSource mInstallSource;
+
@GuardedBy("mLock")
private float mClientProgress = 0;
@GuardedBy("mLock")
@@ -413,7 +419,8 @@
Context context, PackageManagerService pm,
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId,
- String installerPackageName, int installerUid, SessionParams params, long createdMillis,
+ String installerPackageName, int installerUid, @NonNull InstallSource installSource,
+ SessionParams params, long createdMillis,
File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
@@ -430,6 +437,7 @@
mOriginalInstallerUid = installerUid;
mInstallerPackageName = installerPackageName;
mInstallerUid = installerUid;
+ mInstallSource = Preconditions.checkNotNull(installSource);
this.params = params;
this.createdMillis = createdMillis;
this.updatedMillis = createdMillis;
@@ -1225,6 +1233,7 @@
mInstallerPackageName = packageName;
mInstallerUid = newOwnerAppInfo.uid;
+ mInstallSource = InstallSource.create(packageName);
}
// Persist the fact that we've sealed ourselves to prevent
@@ -1443,7 +1452,7 @@
mRelinquished = true;
return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
- localObserver, params, mInstallerPackageName, mInstallerUid, user,
+ localObserver, params, mInstallerPackageName, mInstallerUid, mInstallSource, user,
mSigningDetails);
}
@@ -2336,6 +2345,7 @@
pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
pw.printPair("mInstallerPackageName", mInstallerPackageName);
pw.printPair("mInstallerUid", mInstallerUid);
+ mInstallSource.dump(pw);
pw.printPair("createdMillis", createdMillis);
pw.printPair("updatedMillis", updatedMillis);
pw.printPair("stageDir", stageDir);
@@ -2416,6 +2426,8 @@
writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
mInstallerPackageName);
writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid);
+ writeStringAttribute(out, ATTR_INITIATING_PACKAGE_NAME,
+ mInstallSource.initiatingPackageName);
writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis);
writeLongAttribute(out, ATTR_UPDATED_MILLIS, updatedMillis);
if (stageDir != null) {
@@ -2521,6 +2533,8 @@
final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, pm.getPackageUid(
installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
+ final String installInitiatingPackageName =
+ readStringAttribute(in, ATTR_INITIATING_PACKAGE_NAME);
final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
long updatedMillis = readLongAttribute(in, ATTR_UPDATED_MILLIS);
final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
@@ -2612,17 +2626,11 @@
childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
}
+ InstallSource installSource = InstallSource.create(installInitiatingPackageName);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerPackageName,
- installerUid, params, createdMillis, stageDir, stageCid, prepared, committed,
- sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- stagedSessionErrorCode, stagedSessionErrorMessage);
- }
-
- /**
- * Reads the session ID from a child session tag stored in the provided {@link XmlPullParser}
- */
- static int readChildSessionIdFromXml(@NonNull XmlPullParser in) {
- return readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID);
+ installerUid, installSource, params, createdMillis, stageDir, stageCid,
+ prepared, committed, sealed, childSessionIdsArray, parentSessionId,
+ isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d675e36..d057aa2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -974,124 +974,9 @@
@Override public final boolean hasFeature(String feature) {
return PackageManagerService.this.hasSystemFeature(feature, 0);
}
-
- final List<PackageParser.Package> getStaticOverlayPackages(
- Collection<PackageParser.Package> allPackages, String targetPackageName) {
- if ("android".equals(targetPackageName)) {
- // Static RROs targeting to "android", ie framework-res.apk, are already applied by
- // native AssetManager.
- return null;
- }
-
- List<PackageParser.Package> overlayPackages = null;
- for (PackageParser.Package p : allPackages) {
- if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) {
- if (overlayPackages == null) {
- overlayPackages = new ArrayList<>();
- }
- overlayPackages.add(p);
- }
- }
- if (overlayPackages != null) {
- Comparator<PackageParser.Package> cmp =
- Comparator.comparingInt(p -> p.mOverlayPriority);
- overlayPackages.sort(cmp);
- }
- return overlayPackages;
- }
-
- final String[] getStaticOverlayPaths(List<PackageParser.Package> overlayPackages,
- String targetPath) {
- if (overlayPackages == null || overlayPackages.isEmpty()) {
- return null;
- }
- List<String> overlayPathList = null;
- for (PackageParser.Package overlayPackage : overlayPackages) {
- if (targetPath == null) {
- if (overlayPathList == null) {
- overlayPathList = new ArrayList<>();
- }
- overlayPathList.add(overlayPackage.baseCodePath);
- continue;
- }
-
- try {
- // Creates idmaps for system to parse correctly the Android manifest of the
- // target package.
- //
- // OverlayManagerService will update each of them with a correct gid from its
- // target package app id.
- mInstaller.idmap(targetPath, overlayPackage.baseCodePath,
- UserHandle.getSharedAppGid(
- UserHandle.getUserGid(UserHandle.USER_SYSTEM)));
- if (overlayPathList == null) {
- overlayPathList = new ArrayList<>();
- }
- overlayPathList.add(overlayPackage.baseCodePath);
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to generate idmap for " + targetPath + " and " +
- overlayPackage.baseCodePath);
- }
- }
- return overlayPathList == null ? null : overlayPathList.toArray(new String[0]);
- }
-
- String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
- List<PackageParser.Package> overlayPackages;
- synchronized (mInstallLock) {
- synchronized (mLock) {
- overlayPackages = getStaticOverlayPackages(
- mPackages.values(), targetPackageName);
- }
- // It is safe to keep overlayPackages without holding mPackages because static overlay
- // packages can't be uninstalled or disabled.
- return getStaticOverlayPaths(overlayPackages, targetPath);
- }
- }
-
- @Override public final String[] getOverlayApks(String targetPackageName) {
- return getStaticOverlayPaths(targetPackageName, null);
- }
-
- @Override public final String[] getOverlayPaths(String targetPackageName,
- String targetPath) {
- return getStaticOverlayPaths(targetPackageName, targetPath);
- }
- }
-
- class ParallelPackageParserCallback extends PackageParserCallback {
- List<PackageParser.Package> mOverlayPackages = null;
-
- void findStaticOverlayPackages() {
- synchronized (mLock) {
- for (PackageParser.Package p : mPackages.values()) {
- if (p.mOverlayIsStatic) {
- if (mOverlayPackages == null) {
- mOverlayPackages = new ArrayList<>();
- }
- mOverlayPackages.add(p);
- }
- }
- }
- }
-
- @Override
- synchronized String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
- // We can trust mOverlayPackages without holding mPackages because package uninstall
- // can't happen while running parallel parsing.
- // And we can call mInstaller inside getStaticOverlayPaths without holding mInstallLock
- // because mInstallLock is held before running parallel parsing.
- // Moreover holding mPackages or mInstallLock on each parsing thread causes dead-lock.
- return mOverlayPackages == null ? null :
- getStaticOverlayPaths(
- getStaticOverlayPackages(mOverlayPackages, targetPackageName),
- targetPath);
- }
}
final PackageParser.Callback mPackageParserCallback = new PackageParserCallback();
- final ParallelPackageParserCallback mParallelPackageParserCallback =
- new ParallelPackageParserCallback();
// Currently known shared libraries.
final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>();
@@ -1603,6 +1488,8 @@
final @Nullable String mConfiguratorPackage;
final @Nullable String mAppPredictionServicePackage;
final @Nullable String mIncidentReportApproverPackage;
+ final @Nullable String[] mTelephonyPackages;
+ final @Nullable String mWifiPackage;
final @NonNull String mServicesSystemSharedLibraryPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
@@ -1675,7 +1562,8 @@
}
// Send broadcasts
for (int i = 0; i < size; i++) {
- sendPackageChangedBroadcast(packages[i], true, components[i], uids[i]);
+ sendPackageChangedBroadcast(packages[i], true, components[i], uids[i],
+ null);
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
break;
@@ -2163,7 +2051,7 @@
// send broadcast that all consumers of the static shared library have changed
sendPackageChangedBroadcast(pkg.packageName, false /*killFlag*/,
new ArrayList<>(Collections.singletonList(pkg.packageName)),
- pkg.applicationInfo.uid);
+ pkg.applicationInfo.uid, null);
}
}
@@ -2805,8 +2693,6 @@
systemScanFlags | partition.scanFlag, 0);
}
- mParallelPackageParserCallback.findStaticOverlayPackages();
-
scanDirTracedLI(frameworkDir, systemParseFlags,
systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
if (!mPackages.containsKey("android")) {
@@ -3058,6 +2944,8 @@
mContext.getString(R.string.config_deviceConfiguratorPackageName);
mAppPredictionServicePackage = getAppPredictionServicePackageName();
mIncidentReportApproverPackage = getIncidentReportApproverPackageName();
+ mTelephonyPackages = getTelephonyPackageNames();
+ mWifiPackage = mContext.getString(R.string.config_wifiPackage);
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
@@ -8466,7 +8354,7 @@
}
try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
- mParallelPackageParserCallback)) {
+ mPackageParserCallback)) {
// Submit files for parsing in parallel
int fileCount = 0;
for (File file : files) {
@@ -10682,6 +10570,50 @@
return changedAbiCodePath;
}
+ /**
+ * Sets the enabled state of components configured through {@link SystemConfig}.
+ * This modifies the {@link PackageSetting} object.
+ **/
+ static void configurePackageComponents(PackageParser.Package pkg) {
+ final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
+ .getComponentsEnabledStates(pkg.packageName);
+ if (componentsEnabledStates == null) {
+ return;
+ }
+
+ for (int i = pkg.activities.size() - 1; i >= 0; i--) {
+ final PackageParser.Activity component = pkg.activities.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+
+ for (int i = pkg.receivers.size() - 1; i >= 0; i--) {
+ final PackageParser.Activity component = pkg.receivers.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+
+ for (int i = pkg.providers.size() - 1; i >= 0; i--) {
+ final PackageParser.Provider component = pkg.providers.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+
+ for (int i = pkg.services.size() - 1; i >= 0; i--) {
+ final PackageParser.Service component = pkg.services.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+ }
+
/**
* Just scans the package without any side effects.
@@ -10849,6 +10781,10 @@
pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM);
}
+ if (pkg.isSystem()) {
+ configurePackageComponents(pkg);
+ }
+
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
@@ -13979,6 +13915,7 @@
final IPackageInstallObserver2 observer;
int installFlags;
final String installerPackageName;
+ final InstallSource installSource;
final String volumeUuid;
private boolean mVerificationCompleted;
private boolean mEnableRollbackCompleted;
@@ -13995,10 +13932,11 @@
final long requiredInstalledVersionCode;
InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
- int installFlags, String installerPackageName, String volumeUuid,
+ int installFlags, String installerPackageName,
+ InstallSource installSource, String volumeUuid,
VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
- PackageParser.SigningDetails signingDetails, int installReason,
+ SigningDetails signingDetails, int installReason,
long requiredInstalledVersionCode) {
super(user);
this.origin = origin;
@@ -14006,6 +13944,7 @@
this.observer = observer;
this.installFlags = installFlags;
this.installerPackageName = installerPackageName;
+ this.installSource = installSource;
this.volumeUuid = volumeUuid;
this.verificationInfo = verificationInfo;
this.packageAbiOverride = packageAbiOverride;
@@ -14037,6 +13976,7 @@
observer = activeInstallSession.getObserver();
installFlags = activeInstallSession.getSessionParams().installFlags;
installerPackageName = activeInstallSession.getInstallerPackageName();
+ installSource = activeInstallSession.getInstallSource();
volumeUuid = activeInstallSession.getSessionParams().volumeUuid;
packageAbiOverride = activeInstallSession.getSessionParams().abiOverride;
grantedRuntimePermissions = activeInstallSession.getSessionParams()
@@ -14519,6 +14459,7 @@
// Always refers to PackageManager flags only
final int installFlags;
final String installerPackageName;
+ final InstallSource installSource;
final String volumeUuid;
final UserHandle user;
final String abiOverride;
@@ -14537,7 +14478,8 @@
/* nullable */ String[] instructionSets;
InstallArgs(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
- int installFlags, String installerPackageName, String volumeUuid,
+ int installFlags, String installerPackageName,
+ InstallSource installSource, String volumeUuid,
UserHandle user, String[] instructionSets,
String abiOverride, String[] installGrantPermissions,
List<String> whitelistedRestrictedPermissions,
@@ -14549,6 +14491,7 @@
this.installFlags = installFlags;
this.observer = observer;
this.installerPackageName = installerPackageName;
+ this.installSource = installSource;
this.volumeUuid = volumeUuid;
this.user = user;
this.instructionSets = instructionSets;
@@ -14562,6 +14505,16 @@
this.mMultiPackageInstallParams = multiPackageInstallParams;
}
+ /** New install */
+ InstallArgs(InstallParams params) {
+ this(params.origin, params.move, params.observer, params.installFlags,
+ params.installerPackageName, params.installSource, params.volumeUuid,
+ params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
+ params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
+ params.traceMethod, params.traceCookie, params.signingDetails,
+ params.installReason, params.mParentInstallParams);
+ }
+
abstract int copyApk();
abstract int doPreInstall(int status);
@@ -14642,17 +14595,12 @@
/** New install */
FileInstallArgs(InstallParams params) {
- super(params.origin, params.move, params.observer, params.installFlags,
- params.installerPackageName, params.volumeUuid,
- params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
- params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
- params.traceMethod, params.traceCookie, params.signingDetails,
- params.installReason, params.mParentInstallParams);
+ super(params);
}
/** Existing install */
FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
- super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets,
+ super(OriginInfo.fromNothing(), null, null, 0, null, null, null, null, instructionSets,
null, null, null, null, 0, PackageParser.SigningDetails.UNKNOWN,
PackageManager.INSTALL_REASON_UNKNOWN, null /* parent */);
this.codeFile = (codePath != null) ? new File(codePath) : null;
@@ -14831,12 +14779,7 @@
/** New install */
MoveInstallArgs(InstallParams params) {
- super(params.origin, params.move, params.observer, params.installFlags,
- params.installerPackageName, params.volumeUuid,
- params.getUser(), null /* instruction sets */, params.packageAbiOverride,
- params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
- params.traceMethod, params.traceCookie, params.signingDetails,
- params.installReason, params.mParentInstallParams);
+ super(params);
}
int copyApk() {
@@ -15095,38 +15038,38 @@
return disabled;
}
- private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
- int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) {
+ private void updateSettingsLI(PackageParser.Package newPackage,
+ InstallArgs installArgs, int[] allUsers, PackageInstalledInfo res) {
// Update the parent package setting
- updateSettingsInternalLI(newPackage, installerPackageName, allUsers, res.origUsers,
- res, user, installReason);
+ updateSettingsInternalLI(newPackage, installArgs, allUsers, res);
// Update the child packages setting
final int childCount = (newPackage.childPackages != null)
? newPackage.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPackage = newPackage.childPackages.get(i);
PackageInstalledInfo childRes = res.addedChildPackages.get(childPackage.packageName);
- updateSettingsInternalLI(childPackage, installerPackageName, allUsers,
- childRes.origUsers, childRes, user, installReason);
+ updateSettingsInternalLI(childPackage, installArgs, allUsers, childRes);
}
}
private void updateSettingsInternalLI(PackageParser.Package pkg,
- String installerPackageName, int[] allUsers, int[] installedForUsers,
- PackageInstalledInfo res, UserHandle user, int installReason) {
+ InstallArgs installArgs, int[] allUsers, PackageInstalledInfo res) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
final String pkgName = pkg.packageName;
+ final String installerPackageName = installArgs.installerPackageName;
+ final int[] installedForUsers = res.origUsers;
+ final int installReason = installArgs.installReason;
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
synchronized (mLock) {
// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
- mPermissionManager.updatePermissions(pkg.packageName, pkg);
+ mPermissionManager.updatePermissions(pkgName, pkg);
// For system-bundled packages, we assume that installing an upgraded version
// of the package implies that the user actually wants to run that new code,
// so we enable the package.
PackageSetting ps = mSettings.mPackages.get(pkgName);
- final int userId = user.getIdentifier();
+ final int userId = installArgs.user.getIdentifier();
if (ps != null) {
if (isSystemApp(pkg)) {
if (DEBUG_INSTALL) {
@@ -15162,6 +15105,9 @@
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
}
+ ps.setInstallSource(installArgs.installSource);
+
+
// When replacing an existing package, preserve the original install reason for all
// users that had the package installed before.
final Set<Integer> previousUserIds = new ArraySet<>();
@@ -15805,8 +15751,7 @@
}
commitReconciledScanResultLocked(reconciledPkg);
- updateSettingsLI(pkg, reconciledPkg.installArgs.installerPackageName, request.mAllUsers,
- res, reconciledPkg.installArgs.user, reconciledPkg.installArgs.installReason);
+ updateSettingsLI(pkg, reconciledPkg.installArgs, request.mAllUsers, res);
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
@@ -15816,8 +15761,7 @@
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPkg = pkg.childPackages.get(i);
- PackageInstalledInfo childRes = res.addedChildPackages.get(
- childPkg.packageName);
+ PackageInstalledInfo childRes = res.addedChildPackages.get(childPkg.packageName);
PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName);
if (childPs != null) {
childRes.newUsers = childPs.queryInstalledUsers(
@@ -16064,10 +16008,6 @@
* will be used to scan and reconcile the package.
*/
private static class PrepareResult {
- public final int installReason;
- public final String volumeUuid;
- public final String installerPackageName;
- public final UserHandle user;
public final boolean replace;
public final int scanFlags;
public final int parseFlags;
@@ -16076,24 +16016,16 @@
public final PackageParser.Package packageToScan;
public final boolean clearCodeCache;
public final boolean system;
- /* The original package name if it was changed during an update, otherwise {@code null}. */
- @Nullable
- public final String renamedPackage;
public final PackageFreezer freezer;
public final PackageSetting originalPs;
public final PackageSetting disabledPs;
public final PackageSetting[] childPackageSettings;
- private PrepareResult(int installReason, String volumeUuid,
- String installerPackageName, UserHandle user, boolean replace, int scanFlags,
+ private PrepareResult(boolean replace, int scanFlags,
int parseFlags, PackageParser.Package existingPackage,
PackageParser.Package packageToScan, boolean clearCodeCache, boolean system,
- String renamedPackage, PackageFreezer freezer, PackageSetting originalPs,
+ PackageFreezer freezer, PackageSetting originalPs,
PackageSetting disabledPs, PackageSetting[] childPackageSettings) {
- this.installReason = installReason;
- this.volumeUuid = volumeUuid;
- this.installerPackageName = installerPackageName;
- this.user = user;
this.replace = replace;
this.scanFlags = scanFlags;
this.parseFlags = parseFlags;
@@ -16101,7 +16033,6 @@
this.packageToScan = packageToScan;
this.clearCodeCache = clearCodeCache;
this.system = system;
- this.renamedPackage = renamedPackage;
this.freezer = freezer;
this.originalPs = originalPs;
this.disabledPs = disabledPs;
@@ -16141,8 +16072,6 @@
private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
throws PrepareFailure {
final int installFlags = args.installFlags;
- final String installerPackageName = args.installerPackageName;
- final String volumeUuid = args.volumeUuid;
final File tmpPackageFile = new File(args.getCodePath());
final boolean onExternal = args.volumeUuid != null;
final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
@@ -16567,14 +16496,12 @@
final PackageParser.Package existingPackage;
String renamedPackage = null;
boolean sysPkg = false;
- String targetVolumeUuid = volumeUuid;
int targetScanFlags = scanFlags;
int targetParseFlags = parseFlags;
final PackageSetting ps;
final PackageSetting disabledPs;
final PackageSetting[] childPackages;
if (replace) {
- targetVolumeUuid = null;
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// Static libs have a synthetic package name containing the version
// and cannot be updated as an update would get a new package name,
@@ -16826,9 +16753,8 @@
// we're passing the freezer back to be closed in a later phase of install
shouldCloseFreezerBeforeReturn = false;
- return new PrepareResult(args.installReason, targetVolumeUuid, installerPackageName,
- args.user, replace, targetScanFlags, targetParseFlags, existingPackage, pkg,
- replace /* clearCodeCache */, sysPkg, renamedPackage, freezer,
+ return new PrepareResult(replace, targetScanFlags, targetParseFlags,
+ existingPackage, pkg, replace /* clearCodeCache */, sysPkg, freezer,
ps, disabledPs, childPackages);
} finally {
if (shouldCloseFreezerBeforeReturn) {
@@ -17509,7 +17435,7 @@
continue;
}
List<VersionedPackage> libClientPackages = getPackagesUsingSharedLibraryLPr(
- libraryInfo, 0, currUserId);
+ libraryInfo, MATCH_KNOWN_PACKAGES, currUserId);
if (!ArrayUtils.isEmpty(libClientPackages)) {
Slog.w(TAG, "Not removing package " + pkg.manifestPackageName
+ " hosting lib " + libraryInfo.getName() + " version "
@@ -19797,6 +19723,16 @@
}
@Override
+ public String[] getTelephonyPackageNames() {
+ String names = mContext.getString(R.string.config_telephonyPackages);
+ String[] telephonyPackageNames = null;
+ if (!TextUtils.isEmpty(names)) {
+ telephonyPackageNames = names.trim().split(",");
+ }
+ return telephonyPackageNames;
+ }
+
+ @Override
public void setApplicationEnabledSetting(String appPackageName,
int newState, int flags, int userId, String callingPackage) {
if (!mUserManager.exists(userId)) return;
@@ -20039,7 +19975,7 @@
if (sendNow) {
int packageUid = UserHandle.getUid(userId, pkgSetting.appId);
sendPackageChangedBroadcast(packageName,
- (flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
+ (flags & PackageManager.DONT_KILL_APP) != 0, components, packageUid, null);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -20071,7 +20007,8 @@
}
private void sendPackageChangedBroadcast(String packageName,
- boolean killFlag, ArrayList<String> componentNames, int packageUid) {
+ boolean killFlag, ArrayList<String> componentNames, int packageUid,
+ String reason) {
if (DEBUG_INSTALL)
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+ componentNames);
@@ -20082,6 +20019,9 @@
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
extras.putInt(Intent.EXTRA_UID, packageUid);
+ if (reason != null) {
+ extras.putString(Intent.EXTRA_REASON, reason);
+ }
// If this is not reporting a change of the overall package, then only send it
// to registered receivers. We don't want to launch a swath of apps for every
// little component state change.
@@ -20329,6 +20269,34 @@
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
+ IntentFilter overlayFilter = new IntentFilter(Intent.ACTION_OVERLAY_CHANGED);
+ overlayFilter.addDataScheme("package");
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ Uri data = intent.getData();
+ if (data == null) {
+ return;
+ }
+ String packageName = data.getSchemeSpecificPart();
+ if (packageName == null) {
+ return;
+ }
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ return;
+ }
+ sendPackageChangedBroadcast(pkg.packageName,
+ false /* killFlag */,
+ new ArrayList<>(Collections.singletonList(pkg.packageName)),
+ pkg.applicationInfo.uid,
+ Intent.ACTION_OVERLAY_CHANGED);
+ }
+ }, overlayFilter);
+
mModuleInfoProvider.systemReady();
// Installer service might attempt to install some packages that have been staged for
@@ -21909,6 +21877,7 @@
final String currentVolumeUuid;
final File codeFile;
final String installerPackageName;
+ final InstallSource installSource;
final String packageAbiOverride;
final int appId;
final String seinfo;
@@ -21966,6 +21935,7 @@
isCurrentLocationExternal = isExternal(pkg);
codeFile = new File(pkg.codePath);
installerPackageName = ps.installerPackageName;
+ installSource = ps.installSource;
packageAbiOverride = ps.cpuAbiOverrideString;
appId = UserHandle.getAppId(pkg.applicationInfo.uid);
seinfo = pkg.applicationInfo.seInfo;
@@ -22111,7 +22081,7 @@
final Message msg = mHandler.obtainMessage(INIT_COPY);
final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
final InstallParams params = new InstallParams(origin, move, installObserver, installFlags,
- installerPackageName, volumeUuid, null /*verificationInfo*/, user,
+ installerPackageName, installSource, volumeUuid, null /*verificationInfo*/, user,
packageAbiOverride, null /*grantedPermissions*/,
null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN,
PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST);
@@ -22869,34 +22839,38 @@
}
@Override
- public String getKnownPackageName(int knownPackage, int userId) {
+ public @NonNull String[] getKnownPackageNames(int knownPackage, int userId) {
switch(knownPackage) {
case PackageManagerInternal.PACKAGE_BROWSER:
- return mPermissionManager.getDefaultBrowser(userId);
+ return new String[]{mPermissionManager.getDefaultBrowser(userId)};
case PackageManagerInternal.PACKAGE_INSTALLER:
- return mRequiredInstallerPackage;
+ return new String[]{mRequiredInstallerPackage};
case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
- return mSetupWizardPackage;
+ return new String[]{mSetupWizardPackage};
case PackageManagerInternal.PACKAGE_SYSTEM:
- return "android";
+ return new String[]{"android"};
case PackageManagerInternal.PACKAGE_VERIFIER:
- return mRequiredVerifierPackage;
+ return new String[]{mRequiredVerifierPackage};
case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
- return mSystemTextClassifierPackage;
+ return new String[]{mSystemTextClassifierPackage};
case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
- return mRequiredPermissionControllerPackage;
+ return new String[]{mRequiredPermissionControllerPackage};
case PackageManagerInternal.PACKAGE_WELLBEING:
- return mWellbeingPackage;
+ return new String[]{mWellbeingPackage};
case PackageManagerInternal.PACKAGE_DOCUMENTER:
- return mDocumenterPackage;
+ return new String[]{mDocumenterPackage};
case PackageManagerInternal.PACKAGE_CONFIGURATOR:
- return mConfiguratorPackage;
+ return new String[]{mConfiguratorPackage};
case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
- return mIncidentReportApproverPackage;
+ return new String[]{mIncidentReportApproverPackage};
case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
- return mAppPredictionServicePackage;
+ return new String[]{mAppPredictionServicePackage};
+ case PackageManagerInternal.PACKAGE_TELEPHONY:
+ return mTelephonyPackages;
+ case PackageManagerInternal.PACKAGE_WIFI:
+ return new String[]{mWifiPackage};
}
- return null;
+ return ArrayUtils.emptyArray(String.class);
}
@Override
@@ -23994,18 +23968,21 @@
private final PackageInstaller.SessionParams mSessionParams;
private final String mInstallerPackageName;
private final int mInstallerUid;
+ private final InstallSource mInstallSource;
private final UserHandle mUser;
private final SigningDetails mSigningDetails;
ActiveInstallSession(String packageName, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, String installerPackageName,
- int installerUid, UserHandle user, SigningDetails signingDetails) {
+ int installerUid, InstallSource installSource,
+ UserHandle user, SigningDetails signingDetails) {
mPackageName = packageName;
mStagedDir = stagedDir;
mObserver = observer;
mSessionParams = sessionParams;
mInstallerPackageName = installerPackageName;
mInstallerUid = installerUid;
+ mInstallSource = installSource;
mUser = user;
mSigningDetails = signingDetails;
}
@@ -24034,6 +24011,10 @@
return mInstallerUid;
}
+ public InstallSource getInstallSource() {
+ return mInstallSource;
+ }
+
public UserHandle getUser() {
return mUser;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1c1c947..52a7c6e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -468,7 +468,7 @@
* @param pckg
*/
private int displayPackageFilePath(String pckg, int userId) throws RemoteException {
- PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId);
+ PackageInfo info = mInterface.getPackageInfo(pckg, PackageManager.MATCH_APEX, userId);
if (info != null && info.applicationInfo != null) {
final PrintWriter pw = getOutPrintWriter();
pw.print("package:");
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 4ea8a30..be0621b 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -205,6 +205,11 @@
proto.end(splitToken);
}
}
+
+ long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
+ proto.write(PackageProto.InstallSourceProto.INITIATING_PACKAGE_NAME,
+ installSource.initiatingPackageName);
+ proto.end(sourceToken);
}
writeUsersInfoToProto(proto, PackageProto.USERS);
proto.end(packageToken);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0da6b54..f0857dd 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
@@ -125,6 +126,9 @@
String installerPackageName;
/** Indicates if the package that installed this app has been uninstalled */
boolean isOrphaned;
+ /** Information about the initial install of this package. */
+ @NonNull
+ InstallSource installSource;
/** UUID of {@link VolumeInfo} hosting this app */
String volumeUuid;
/** The category of this app, as hinted by the installer */
@@ -148,8 +152,17 @@
? new ArrayList<>(childPackageNames) : null;
this.usesStaticLibraries = usesStaticLibraries;
this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
- init(codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString,
- secondaryCpuAbiString, cpuAbiOverrideString, pVersionCode);
+ this.codePath = codePath;
+ this.codePathString = codePath.toString();
+ this.resourcePath = resourcePath;
+ this.resourcePathString = resourcePath.toString();
+ this.legacyNativeLibraryPathString = legacyNativeLibraryPathString;
+ this.primaryCpuAbiString = primaryCpuAbiString;
+ this.secondaryCpuAbiString = secondaryCpuAbiString;
+ this.cpuAbiOverrideString = cpuAbiOverrideString;
+ this.versionCode = pVersionCode;
+ this.signatures = new PackageSignatures();
+ this.installSource = InstallSource.create(null);
}
/**
@@ -166,21 +179,6 @@
doCopy(base);
}
- void init(File codePath, File resourcePath, String legacyNativeLibraryPathString,
- String primaryCpuAbiString, String secondaryCpuAbiString,
- String cpuAbiOverrideString, long pVersionCode) {
- this.codePath = codePath;
- this.codePathString = codePath.toString();
- this.resourcePath = resourcePath;
- this.resourcePathString = resourcePath.toString();
- this.legacyNativeLibraryPathString = legacyNativeLibraryPathString;
- this.primaryCpuAbiString = primaryCpuAbiString;
- this.secondaryCpuAbiString = secondaryCpuAbiString;
- this.cpuAbiOverrideString = cpuAbiOverrideString;
- this.versionCode = pVersionCode;
- this.signatures = new PackageSignatures();
- }
-
public void setInstallerPackageName(String packageName) {
installerPackageName = packageName;
}
@@ -189,6 +187,21 @@
return installerPackageName;
}
+ public void setInstallSource(InstallSource installSource) {
+ this.installSource = installSource == null ? InstallSource.create(null) : installSource;
+ }
+
+ void removeInstallerPackage(String packageName) {
+ if (packageName == null) {
+ return;
+ }
+ if (packageName.equals(installerPackageName)) {
+ installerPackageName = null;
+ isOrphaned = true;
+ }
+ installSource = installSource.removeInstallerPackage(packageName);
+ }
+
public void setVolumeUuid(String volumeUuid) {
this.volumeUuid = volumeUuid;
}
@@ -241,6 +254,7 @@
firstInstallTime = orig.firstInstallTime;
installPermissionsFixed = orig.installPermissionsFixed;
installerPackageName = orig.installerPackageName;
+ installSource = orig.installSource;
isOrphaned = orig.isOrphaned;
keySetData = orig.keySetData;
lastUpdateTime = orig.lastUpdateTime;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0db6e79..5e209965 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -554,10 +554,6 @@
return null;
}
- void addAppOpPackage(String permName, String packageName) {
- mPermissions.addAppOpPackage(permName, packageName);
- }
-
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
@@ -1052,13 +1048,7 @@
return;
}
for (int i = 0; i < mPackages.size(); i++) {
- final PackageSetting ps = mPackages.valueAt(i);
- final String installerPackageName = ps.getInstallerPackageName();
- if (installerPackageName != null
- && installerPackageName.equals(packageName)) {
- ps.setInstallerPackageName(null);
- ps.isOrphaned = true;
- }
+ mPackages.valueAt(i).removeInstallerPackage(packageName);
}
mInstallerPackages.remove(packageName);
}
@@ -2854,12 +2844,15 @@
if (pkg.isOrphaned) {
serializer.attribute(null, "isOrphaned", "true");
}
+ InstallSource installSource = pkg.installSource;
+ if (installSource.initiatingPackageName != null) {
+ serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
+ }
if (pkg.volumeUuid != null) {
serializer.attribute(null, "volumeUuid", pkg.volumeUuid);
}
if (pkg.categoryHint != ApplicationInfo.CATEGORY_UNDEFINED) {
- serializer.attribute(null, "categoryHint",
- Integer.toString(pkg.categoryHint));
+ serializer.attribute(null, "categoryHint", Integer.toString(pkg.categoryHint));
}
if (pkg.parentPackageName != null) {
serializer.attribute(null, "parentPackageName", pkg.parentPackageName);
@@ -2874,8 +2867,7 @@
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
- writePermissionsLPr(serializer, pkg.getPermissionsState()
- .getInstallPermissionStates());
+ writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
@@ -3613,6 +3605,7 @@
String systemStr = null;
String installerPackageName = null;
String isOrphaned = null;
+ String installInitiatingPackageName = null;
String volumeUuid = null;
String categoryHintString = null;
String updateAvailable = null;
@@ -3659,6 +3652,7 @@
}
installerPackageName = parser.getAttributeValue(null, "installer");
isOrphaned = parser.getAttributeValue(null, "isOrphaned");
+ installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator");
volumeUuid = parser.getAttributeValue(null, "volumeUuid");
categoryHintString = parser.getAttributeValue(null, "categoryHint");
if (categoryHintString != null) {
@@ -3815,6 +3809,7 @@
packageSetting.uidError = "true".equals(uidError);
packageSetting.installerPackageName = installerPackageName;
packageSetting.isOrphaned = "true".equals(isOrphaned);
+ packageSetting.installSource = InstallSource.create(installInitiatingPackageName);
packageSetting.volumeUuid = volumeUuid;
packageSetting.categoryHint = categoryHint;
packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 036d1e8..323c957 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -449,7 +449,14 @@
}
void dump(PrintWriter pw) {
- for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+ pw.print("Whitelisted packages per user type");
+ final int size = mWhitelitsedPackagesForUserTypes.size();
+ if (size == 0) {
+ pw.println(": N/A");
+ return;
+ }
+ pw.println(" (" + size + " packages)");
+ for (int i = 0; i < size; i++) {
final String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
final String whitelistedUserTypes =
UserInfo.flagsToString(mWhitelitsedPackagesForUserTypes.valueAt(i));
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 6d22faa..037912a 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -276,6 +276,12 @@
public boolean isAppPredictor() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0;
}
+ public boolean isTelephony() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0;
+ }
+ public boolean isWifi() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_WIFI) != 0;
+ }
public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
if (!origPackageName.equals(sourcePackageName)) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 793cdd2..f247037 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -437,17 +437,20 @@
// Installer
grantSystemFixedPermissionsToSystemPackage(
- getKnownPackage(PackageManagerInternal.PACKAGE_INSTALLER, userId),
+ ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_INSTALLER, userId)),
userId, STORAGE_PERMISSIONS);
// Verifier
- final String verifier = getKnownPackage(PackageManagerInternal.PACKAGE_VERIFIER, userId);
+ final String verifier = ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_VERIFIER, userId));
grantSystemFixedPermissionsToSystemPackage(verifier, userId, STORAGE_PERMISSIONS);
grantPermissionsToSystemPackage(verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS);
// SetupWizard
grantPermissionsToSystemPackage(
- getKnownPackage(PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId), userId,
+ ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId)), userId,
PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
CAMERA_PERMISSIONS);
@@ -596,7 +599,8 @@
userId, CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS);
// Browser
- String browserPackage = getKnownPackage(PackageManagerInternal.PACKAGE_BROWSER, userId);
+ String browserPackage = ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_BROWSER, userId));
if (browserPackage == null) {
browserPackage = getDefaultSystemHandlerActivityPackageForCategory(
Intent.CATEGORY_APP_BROWSER, userId);
@@ -761,8 +765,8 @@
}
}
- private String getKnownPackage(int knownPkgId, int userId) {
- return mServiceInternal.getKnownPackageName(knownPkgId, userId);
+ private @NonNull String[] getKnownPackages(int knownPkgId, int userId) {
+ return mServiceInternal.getKnownPackageNames(knownPkgId, userId);
}
private void grantDefaultPermissionsToDefaultSystemDialerApp(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 89908f0..5c65752 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -297,7 +297,7 @@
// Critical; after this call the application should never have the permission
mPackageManagerInt.writeSettings(false);
final int appId = UserHandle.getAppId(uid);
- killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+ mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
}
@Override
public void onInstallPermissionRevoked() {
@@ -1902,7 +1902,7 @@
private void restoreRuntimePermissions(@NonNull byte[] backup, @NonNull UserHandle user) {
synchronized (mLock) {
mHasNoDelayedPermBackup.delete(user.getIdentifier());
- mPermissionControllerManager.restoreRuntimePermissionBackup(backup, user);
+ mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup, user);
}
}
@@ -1923,7 +1923,7 @@
return;
}
- mPermissionControllerManager.restoreDelayedRuntimePermissionBackup(packageName, user,
+ mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, user,
mContext.getMainExecutor(), (hasMoreBackup) -> {
if (hasMoreBackup) {
return;
@@ -3078,8 +3078,9 @@
}
}
}
- final String systemPackageName = mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+ // expect single system package
+ String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM));
final PackageParser.Package systemPackage =
mPackageManagerInt.getPackage(systemPackageName);
@@ -3195,18 +3196,19 @@
// need a separate flag anymore. Hence we need to check which
// permissions are needed by the permission controller
if (!allowed && bp.isInstaller()
- && (pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))
- || pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM),
+ pkg.packageName) || ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER,
- UserHandle.USER_SYSTEM)))) {
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// If this permission is to be granted to the system installer and
// this app is an installer, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isVerifier()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// If this permission is to be granted to the system verifier and
// this app is a verifier, then it gets the permission.
allowed = true;
@@ -3222,53 +3224,71 @@
allowed = origPermissions.hasInstallPermission(perm);
}
if (!allowed && bp.isSetup()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// If this permission is to be granted to the system setup wizard and
// this app is a setup wizard, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isSystemTextClassifier()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
- UserHandle.USER_SYSTEM))) {
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// Special permissions for the system default text classifier.
allowed = true;
}
if (!allowed && bp.isConfigurator()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_CONFIGURATOR,
- UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_CONFIGURATOR,
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// Special permissions for the device configurator.
allowed = true;
}
if (!allowed && bp.isWellbeing()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// Special permission granted only to the OEM specified wellbeing app
allowed = true;
}
if (!allowed && bp.isDocumenter()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// If this permission is to be granted to the documenter and
// this app is the documenter, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isIncidentReportApprover()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER,
- UserHandle.USER_SYSTEM))) {
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// If this permission is to be granted to the incident report approver and
// this app is the incident report approver, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isAppPredictor()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// Special permissions for the system app predictor.
allowed = true;
}
+ if (!allowed && bp.isTelephony()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_TELEPHONY, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
+ // Special permissions for the system telephony apps.
+ allowed = true;
+ }
+ if (!allowed && bp.isWifi()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_WIFI, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
+ // Special permissions for the system wifi.
+ allowed = true;
+ }
}
return allowed;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c851cc6..2593c38 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,8 +47,8 @@
import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -2189,10 +2189,10 @@
default:
// These are the windows that by default are shown only to the user that created
// them. If this needs to be overridden, set
- // {@link WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS} in
+ // {@link WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS} in
// {@link WindowManager.LayoutParams}. Note that permission
// {@link android.Manifest.permission.INTERNAL_SYSTEM_WINDOW} is required as well.
- if ((attrs.privateFlags & PRIVATE_FLAG_SHOW_FOR_ALL_USERS) == 0) {
+ if ((attrs.privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS) == 0) {
return true;
}
break;
@@ -2446,7 +2446,7 @@
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
params.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
if (!compatInfo.supportsScreen()) {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3439d38..65bb2342 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -1334,6 +1335,18 @@
}
@Override
+ public void grantInlineReplyUriPermission(String key, Uri uri) {
+ enforceStatusBarService();
+ int callingUid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.grantInlineReplyUriPermission(key, uri, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new StatusBarShellCommand(this, mContext)).exec(
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 9d9a37c..673366f 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -1430,9 +1430,8 @@
continue;
}
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.isActivityTypeHome()
&& ((userId == UserHandle.USER_ALL) || (r.mUserId == userId))) {
return r;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index eff0f75..c6b17e2 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -39,8 +39,13 @@
* If an activity is successfully started, the launch sequence's state will transition into
* {@code STARTED} via {@link #onActivityLaunched}. This is a transient state.
*
- * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled}
- * or into {@code FINISHED} with {@link #onActivityLaunchFinished}. These are terminal states.
+ * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled},
+ * which is a terminal state or into {@code FINISHED} with {@link #onActivityLaunchFinished}.
+ *
+ * The {@code FINISHED} with {@link #onActivityLaunchFinished} then may transition to
+ * {@code FULLY_DRAWN} with {@link #onReportFullyDrawn}, which is a terminal state.
+ * Note this transition may not happen if the reportFullyDrawn event is not receivied,
+ * in which case {@code FINISHED} is terminal.
*
* Note that the {@code ActivityRecordProto} provided as a parameter to some state transitions isn't
* necessarily the same within a single launch sequence: it is only the top-most activity at the
@@ -51,15 +56,15 @@
* until a subsequent transition into {@code INTENT_STARTED} initiates a new launch sequence.
*
* <pre>
- * ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ╔══════════════════════════╗
- * ╴╴▶ ⋮ INTENT_STARTED ⋮ ──▶ ⋮ ACTIVITY_LAUNCHED ⋮ ──▶ ║ ACTIVITY_LAUNCH_FINISHED ║
- * └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ ╚══════════════════════════╝
- * : :
- * : :
- * ▼ ▼
- * ╔════════════════╗ ╔═══════════════════════════╗
- * ║ INTENT_FAILED ║ ║ ACTIVITY_LAUNCH_CANCELLED ║
- * ╚════════════════╝ ╚═══════════════════════════╝
+ * ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ┌--------------------------┐
+ * ╴╴▶ INTENT_STARTED ──▶ ACTIVITY_LAUNCHED ──▶ ACTIVITY_LAUNCH_FINISHED
+ * └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ └--------------------------┘
+ * : : :
+ * : : :
+ * ▼ ▼ ▼
+ * ╔════════════════╗ ╔═══════════════════════════╗ ╔═══════════════════════════╗
+ * ║ INTENT_FAILED ║ ║ ACTIVITY_LAUNCH_CANCELLED ║ ║ REPORT_FULLY_DRAWN ║
+ * ╚════════════════╝ ╚═══════════════════════════╝ ╚═══════════════════════════╝
* </pre>
*/
public interface ActivityMetricsLaunchObserver {
@@ -111,7 +116,7 @@
* Multiple calls to this method cannot occur without first terminating the current
* launch sequence.
*/
- public void onIntentStarted(@NonNull Intent intent);
+ public void onIntentStarted(@NonNull Intent intent, long timestampNanos);
/**
* Notifies the observer that the current launch sequence has failed to launch an activity.
@@ -177,6 +182,9 @@
* drawn for the first time: the top-most activity at the time is what's reported here.
*
* @param finalActivity the top-most activity whose windows were first to fully draw
+ * @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds.
+ * To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted}
+ * from {@code timestampNanos}.
*
* Multiple calls to this method cannot occur without first terminating the current
* launch sequence.
@@ -186,5 +194,22 @@
* and only the latest activity that was top-most during first-frame drawn
* is reported here.
*/
- public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity);
+ public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity,
+ long timestampNanos);
+
+ /**
+ * Notifies the observer that the application self-reported itself as being fully drawn.
+ *
+ * @param activity the activity that triggers the ReportFullyDrawn event.
+ * @param timestampNanos the timestamp of ReportFullyDrawn event in nanoseconds.
+ * To compute the duration, deduct the deduct the timestamp {@link #onIntentStarted}
+ * from {@code timestampNanos}.
+ *
+ * @apiNote The behavior of ReportFullyDrawn mostly depends on the app.
+ * It is used as an accurate estimate of meanfully app startup time.
+ * This event may be missing for many apps.
+ */
+ public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
+ long timestampNanos);
+
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a0a2967..e6c6b12e 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -92,6 +92,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
+import java.util.concurrent.TimeUnit;
/**
* Listens to activity launches, transitions, visibility changes and window drawn callbacks to
@@ -132,8 +133,8 @@
// set to INVALID_START_TIME in reset.
// set to valid value in notifyActivityLaunching
- private long mCurrentTransitionStartTime = INVALID_START_TIME;
- private long mLastTransitionStartTime = INVALID_START_TIME;
+ private long mCurrentTransitionStartTimeNs = INVALID_START_TIME;
+ private long mLastTransitionStartTimeNs = INVALID_START_TIME;
private int mCurrentTransitionDeviceUptime;
private int mCurrentTransitionDelayMs;
@@ -326,12 +327,12 @@
intent));
}
- if (mCurrentTransitionStartTime == INVALID_START_TIME) {
+ if (mCurrentTransitionStartTimeNs == INVALID_START_TIME) {
- mCurrentTransitionStartTime = SystemClock.uptimeMillis();
- mLastTransitionStartTime = mCurrentTransitionStartTime;
+ mCurrentTransitionStartTimeNs = SystemClock.elapsedRealtimeNanos();
+ mLastTransitionStartTimeNs = mCurrentTransitionStartTimeNs;
- launchObserverNotifyIntentStarted(intent);
+ launchObserverNotifyIntentStarted(intent, mCurrentTransitionStartTimeNs);
}
}
@@ -382,14 +383,15 @@
? launchedActivity.getWindowingMode()
: WINDOWING_MODE_UNDEFINED;
final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
- if (mCurrentTransitionStartTime == INVALID_START_TIME) {
+ if (mCurrentTransitionStartTimeNs == INVALID_START_TIME) {
// No transition is active ignore this launch.
return;
}
if (launchedActivity != null && launchedActivity.mDrawn) {
// Launched activity is already visible. We cannot measure windows drawn delay.
- reset(true /* abort */, info, "launched activity already visible");
+ reset(true /* abort */, info, "launched activity already visible",
+ 0L /* timestampNs */);
return;
}
@@ -407,7 +409,8 @@
if ((!isLoggableResultCode(resultCode) || launchedActivity == null || !processSwitch
|| windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) {
// Failed to launch or it was not a process switch, so we don't care about the timing.
- reset(true /* abort */, info, "failed to launch or not a process switch");
+ reset(true /* abort */, info, "failed to launch or not a process switch",
+ 0L /* timestampNs */);
return;
} else if (otherWindowModesLaunching) {
// Don't log this windowing mode but continue with the other windowing modes.
@@ -441,19 +444,20 @@
* Notifies the tracker that all windows of the app have been drawn.
*/
WindowingModeTransitionInfoSnapshot notifyWindowsDrawn(@WindowingMode int windowingMode,
- long timestamp) {
+ long timestampNs) {
if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn windowingMode=" + windowingMode);
final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
if (info == null || info.loggedWindowsDrawn) {
return null;
}
- info.windowsDrawnDelayMs = calculateDelay(timestamp);
+ info.windowsDrawnDelayMs = calculateDelay(timestampNs);
info.loggedWindowsDrawn = true;
final WindowingModeTransitionInfoSnapshot infoSnapshot =
new WindowingModeTransitionInfoSnapshot(info);
if (allWindowsDrawn() && mLoggedTransitionStarting) {
- reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn");
+ reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn",
+ timestampNs /* timestampNs */);
}
return infoSnapshot;
}
@@ -476,7 +480,7 @@
* @param windowingModeToReason A map from windowing mode to a reason integer, which must be on
* of ActivityTaskManagerInternal.APP_TRANSITION_* reasons.
*/
- void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestamp) {
+ void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestampNs) {
if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
// Ignore calls to this made after a reset and prior to notifyActivityLaunching.
@@ -484,7 +488,7 @@
return;
}
if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
- mCurrentTransitionDelayMs = calculateDelay(timestamp);
+ mCurrentTransitionDelayMs = calculateDelay(timestampNs);
mLoggedTransitionStarting = true;
WindowingModeTransitionInfo foundInfo = null;
@@ -501,7 +505,8 @@
if (allWindowsDrawn()) {
// abort metrics collection if we cannot find a matching transition.
final boolean abortMetrics = foundInfo == null;
- reset(abortMetrics, foundInfo, "notifyTransitionStarting - all windows drawn");
+ reset(abortMetrics, foundInfo, "notifyTransitionStarting - all windows drawn",
+ timestampNs /* timestampNs */);
}
}
@@ -527,8 +532,8 @@
}
private boolean hasVisibleNonFinishingActivity(TaskRecord t) {
- for (int i = t.mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = t.mActivities.get(i);
+ for (int i = t.getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = t.getChildAt(i);
if (r.visible && !r.finishing) {
return true;
}
@@ -567,7 +572,8 @@
logAppTransitionCancel(info);
mWindowingModeTransitionInfo.remove(r.getWindowingMode());
if (mWindowingModeTransitionInfo.size() == 0) {
- reset(true /* abort */, info, "notifyVisibilityChanged to invisible");
+ reset(true /* abort */, info, "notifyVisibilityChanged to invisible",
+ 0L /* timestampNs */);
}
}
}
@@ -598,12 +604,16 @@
}
private boolean isAnyTransitionActive() {
- return mCurrentTransitionStartTime != INVALID_START_TIME
+ return mCurrentTransitionStartTimeNs != INVALID_START_TIME
&& mWindowingModeTransitionInfo.size() > 0;
}
- private void reset(boolean abort, WindowingModeTransitionInfo info, String cause) {
- if (DEBUG_METRICS) Slog.i(TAG, "reset abort=" + abort + ",cause=" + cause);
+ private void reset(boolean abort, WindowingModeTransitionInfo info, String cause,
+ long timestampNs) {
+ if (DEBUG_METRICS) {
+ Slog.i(TAG,
+ "reset abort=" + abort + ",cause=" + cause + ",timestamp=" + timestampNs);
+ }
if (!abort && isAnyTransitionActive()) {
logAppTransitionMultiEvents();
}
@@ -615,13 +625,13 @@
if (abort) {
launchObserverNotifyActivityLaunchCancelled(info);
} else {
- launchObserverNotifyActivityLaunchFinished(info);
+ launchObserverNotifyActivityLaunchFinished(info, timestampNs);
}
} else {
launchObserverNotifyIntentFailed();
}
- mCurrentTransitionStartTime = INVALID_START_TIME;
+ mCurrentTransitionStartTimeNs = INVALID_START_TIME;
mCurrentTransitionDelayMs = INVALID_DELAY;
mLoggedTransitionStarting = false;
mWindowingModeTransitionInfo.clear();
@@ -629,12 +639,14 @@
private int calculateCurrentDelay() {
// Shouldn't take more than 25 days to launch an app, so int is fine here.
- return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime);
+ return (int) TimeUnit.NANOSECONDS
+ .toMillis(SystemClock.elapsedRealtimeNanos() - mCurrentTransitionStartTimeNs);
}
- private int calculateDelay(long timestamp) {
+ private int calculateDelay(long timestampNs) {
// Shouldn't take more than 25 days to launch an app, so int is fine here.
- return (int) (timestamp - mCurrentTransitionStartTime);
+ return (int) TimeUnit.NANOSECONDS.toMillis(timestampNs -
+ mCurrentTransitionStartTimeNs);
}
private void logAppTransitionCancel(WindowingModeTransitionInfo info) {
@@ -679,7 +691,7 @@
// Take a snapshot of the transition info before sending it to the handler for logging.
// This will avoid any races with other operations that modify the ActivityRecord.
final WindowingModeTransitionInfoSnapshot infoSnapshot =
- new WindowingModeTransitionInfoSnapshot(info);
+ new WindowingModeTransitionInfoSnapshot(info);
final int currentTransitionDeviceUptime = mCurrentTransitionDeviceUptime;
final int currentTransitionDelayMs = mCurrentTransitionDelayMs;
BackgroundThread.getHandler().post(() -> logAppTransition(
@@ -811,7 +823,9 @@
final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
builder.setPackageName(r.packageName);
builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
- long startupTimeMs = SystemClock.uptimeMillis() - mLastTransitionStartTime;
+ long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
+ long startupTimeMs =
+ TimeUnit.NANOSECONDS.toMillis(currentTimestampNs - mLastTransitionStartTimeNs);
builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
builder.setType(restoredFromBundle
? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
@@ -837,6 +851,10 @@
final WindowingModeTransitionInfoSnapshot infoSnapshot =
new WindowingModeTransitionInfoSnapshot(info, r, (int) startupTimeMs);
BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
+
+ // Notify reportFullyDrawn event.
+ launchObserverNotifyReportFullyDrawn(r, currentTimestampNs);
+
return infoSnapshot;
}
@@ -1006,12 +1024,12 @@
}
/** Notify the {@link ActivityMetricsLaunchObserver} that a new launch sequence has begun. */
- private void launchObserverNotifyIntentStarted(Intent intent) {
+ private void launchObserverNotifyIntentStarted(Intent intent, long timestampNs) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"MetricsLogger:launchObserverNotifyIntentStarted");
// Beginning a launch is timing sensitive and so should be observed as soon as possible.
- mLaunchObserver.onIntentStarted(intent);
+ mLaunchObserver.onIntentStarted(intent, timestampNs);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -1049,6 +1067,16 @@
}
/**
+ * Notifies the {@link ActivityMetricsLaunchObserver} the reportFullDrawn event.
+ */
+ private void launchObserverNotifyReportFullyDrawn(ActivityRecord r, long timestampNs) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "MetricsLogger:launchObserverNotifyReportFullyDrawn");
+ mLaunchObserver.onReportFullyDrawn(convertActivityRecordToProto(r), timestampNs);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /**
* Notify the {@link ActivityMetricsLaunchObserver} that the current launch sequence is
* cancelled.
*/
@@ -1068,12 +1096,14 @@
* Notify the {@link ActivityMetricsLaunchObserver} that the current launch sequence's activity
* has fully finished (successfully).
*/
- private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info) {
+ private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info,
+ long timestampNs) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"MetricsLogger:launchObserverNotifyActivityLaunchFinished");
- mLaunchObserver.onActivityLaunchFinished(
- convertActivityRecordToProto(info.launchedActivity));
+ mLaunchObserver
+ .onActivityLaunchFinished(convertActivityRecordToProto(info.launchedActivity),
+ timestampNs);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4f52d9d..fb4de01 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -144,10 +144,10 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked;
import static com.android.server.wm.TaskPersister.DEBUG;
import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -318,6 +318,7 @@
ArrayList<ResultInfo> results; // pending ActivityResult objs we have received
HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act
ArrayList<ReferrerIntent> newIntents; // any pending new intents for single-top mode
+ Intent mLastNewIntent; // the last new intent we delivered to client
ActivityOptions pendingOptions; // most recently given options
ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
@@ -1253,7 +1254,7 @@
boolean setOccludesParent(boolean occludesParent) {
final boolean changed = super.setOccludesParent(occludesParent);
- if (changed) {
+ if (changed && task != null) {
if (!occludesParent) {
getActivityStack().convertActivityToTranslucent(this);
}
@@ -1577,12 +1578,12 @@
task.mTaskId, shortComponentName, reason);
final ArrayList<ActivityRecord> activities = task.mActivities;
final int index = activities.indexOf(this);
- if (index < (activities.size() - 1)) {
+ if (index < (task.getChildCount() - 1)) {
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
// If the caller asked that this activity (and all above it)
// be cleared when the task is reset, don't lose that information,
// but propagate it up to the next activity.
- final ActivityRecord next = activities.get(index + 1);
+ final ActivityRecord next = task.getChildAt(index + 1);
next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
}
}
@@ -2004,10 +2005,6 @@
if (stopped) {
clearOptionsLocked();
}
-
- if (mAtmService != null) {
- mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
- }
}
/**
@@ -2788,12 +2785,12 @@
if (positionInTask == -1) {
throw new IllegalStateException("Activity not found in its task");
}
- if (positionInTask == task.mActivities.size() - 1) {
+ if (positionInTask == task.getChildCount() - 1) {
// It's the topmost activity in the task - should become resumed now
return true;
}
// Check if activity above is finishing now and this one becomes the topmost in task.
- final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
+ final ActivityRecord activityAbove = task.getChildAt(positionInTask + 1);
if (activityAbove.finishing && results == null) {
// We will only allow making active if activity above wasn't launched for result.
// Otherwise it will cause this activity to resume before getting result.
@@ -2844,11 +2841,14 @@
}
idle = false;
results = null;
+ if (newIntents != null && newIntents.size() > 0) {
+ mLastNewIntent = newIntents.get(newIntents.size() - 1);
+ }
newIntents = null;
stopped = false;
if (isActivityTypeHome()) {
- mStackSupervisor.updateHomeProcess(task.mActivities.get(0).app);
+ mStackSupervisor.updateHomeProcess(task.getChildAt(0).app);
}
if (nowVisible) {
@@ -3102,7 +3102,7 @@
void reportFullyDrawnLocked(boolean restoredFromBundle) {
final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
- .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
+ .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
if (info != null) {
mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
info.windowsFullyDrawnDelayMs, info.getLaunchState());
@@ -3110,13 +3110,13 @@
}
/** Called when the windows associated app window container are drawn. */
- void onWindowsDrawn(boolean drawn, long timestamp) {
+ void onWindowsDrawn(boolean drawn, long timestampNs) {
mDrawn = drawn;
if (!drawn) {
return;
}
final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
- .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp);
+ .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestampNs);
final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
final @LaunchState int launchState = info != null ? info.getLaunchState() : -1;
mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
@@ -3542,7 +3542,7 @@
super.resolveOverrideConfiguration(newParentConfiguration);
// If the activity has override bounds, the relative configuration (e.g. screen size,
// layout) needs to be resolved according to the bounds.
- if (!matchParentBounds()) {
+ if (task != null && !matchParentBounds()) {
task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
newParentConfiguration);
}
@@ -4198,12 +4198,19 @@
return true;
}
- // Restrict task snapshot starting window to launcher start, or there is no intent at all
- // (eg. task being brought to front). If the intent is something else, likely the app is
- // going to show some specific page or view, instead of what's left last time.
+ // Restrict task snapshot starting window to launcher start, or is same as the last
+ // delivered intent, or there is no intent at all (eg. task being brought to front). If
+ // the intent is something else, likely the app is going to show some specific page or
+ // view, instead of what's left last time.
for (int i = newIntents.size() - 1; i >= 0; i--) {
final Intent intent = newIntents.get(i);
- if (intent != null && !ActivityRecord.isMainIntent(intent)) {
+ if (intent == null || ActivityRecord.isMainIntent(intent)) {
+ continue;
+ }
+
+ final boolean sameIntent = mLastNewIntent != null ? mLastNewIntent.filterEquals(intent)
+ : this.intent.filterEquals(intent);
+ if (!sameIntent || intent.getExtras() != null) {
return false;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 5959254..41c1e4e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -33,7 +33,7 @@
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.view.Display.INVALID_DISPLAY;
@@ -488,7 +488,7 @@
int numActivities() {
int count = 0;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- count += mTaskHistory.get(taskNdx).mActivities.size();
+ count += mTaskHistory.get(taskNdx).getChildCount();
}
return count;
}
@@ -1077,9 +1077,8 @@
ActivityRecord topRunningNonOverlayTaskActivity() {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (!r.finishing && !r.mTaskOverlay) {
return r;
}
@@ -1091,9 +1090,8 @@
ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked()) {
return r;
}
@@ -1117,9 +1115,8 @@
if (task.mTaskId == taskId) {
continue;
}
- ArrayList<ActivityRecord> activities = task.mActivities;
- for (int i = activities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = activities.get(i);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
// Note: the taskId check depends on real taskId fields being non-zero
if (!r.finishing && (token != r.appToken) && r.okToShowLocked()) {
return r;
@@ -1431,10 +1428,8 @@
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
-
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (!r.okToShowLocked()) {
continue;
}
@@ -1500,9 +1495,10 @@
void awakeFromSleepingLocked() {
// Ensure activities are no longer sleeping.
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- activities.get(activityNdx).setSleeping(false);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
+ r.setSleeping(false);
}
}
if (mPausingActivity != null) {
@@ -1516,9 +1512,9 @@
final int userId = UserHandle.getUserId(aInfo.uid);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final List<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord ar = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord ar = task.getChildAt(activityNdx);
if ((userId == ar.mUserId) && packageName.equals(ar.packageName)) {
ar.updateApplicationInfo(aInfo);
@@ -1594,9 +1590,9 @@
// Make sure any paused or stopped but visible activities are now sleeping.
// This ensures that the activity's onStop() is called.
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.isState(STARTED, STOPPING, STOPPED, PAUSED, PAUSING)) {
r.setSleeping(true);
}
@@ -1880,9 +1876,8 @@
}
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.finishing) {
// We don't factor in finishing activities when determining translucency since
@@ -2113,9 +2108,8 @@
&& top != null && !top.mLaunchTaskBehind;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
final boolean isTop = r == top;
if (aboveTop && !isTop) {
continue;
@@ -2363,9 +2357,8 @@
void clearOtherAppTimeTrackers(AppTimeTracker except) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if ( r.appTimeTracker != except) {
r.appTimeTracker = null;
}
@@ -2423,9 +2416,9 @@
}
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (aboveTop) {
if (r == topActivity) {
aboveTop = false;
@@ -3210,14 +3203,13 @@
// We only do this for activities that are not the root of the task (since if we finish
// the root, we may no longer have the task!).
- final ArrayList<ActivityRecord> activities = task.mActivities;
- final int numActivities = activities.size();
+ final int numActivities = task.getChildCount();
int lastActivityNdx = task.findRootIndex(true /* effectiveRoot */);
if (lastActivityNdx == -1) {
lastActivityNdx = 0;
}
for (int i = numActivities - 1; i > lastActivityNdx; --i) {
- ActivityRecord target = activities.get(i);
+ ActivityRecord target = task.getChildAt(i);
// TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
if (target.isRootOfTask()) break;
@@ -3257,7 +3249,7 @@
final TaskRecord targetTask;
final ActivityRecord bottom =
!mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ?
- mTaskHistory.get(0).mActivities.get(0) : null;
+ mTaskHistory.get(0).getChildAt(0) : null;
if (bottom != null && target.taskAffinity.equals(bottom.getTaskRecord().affinity)) {
// If the activity currently at the bottom has the
// same task affinity as the one we are moving,
@@ -3279,7 +3271,7 @@
boolean noOptions = canMoveOptions;
final int start = replyChainEnd < 0 ? i : replyChainEnd;
for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = activities.get(srcPos);
+ final ActivityRecord p = task.getChildAt(srcPos);
if (p.finishing) {
continue;
}
@@ -3312,7 +3304,7 @@
// In this case, we want to finish this activity
// and everything above it, so be sneaky and pretend
// like these are all in the reply chain.
- end = activities.size() - 1;
+ end = task.getChildCount() - 1;
} else if (replyChainEnd < 0) {
end = i;
} else {
@@ -3320,7 +3312,7 @@
}
boolean noOptions = canMoveOptions;
for (int srcPos = i; srcPos <= end; srcPos++) {
- ActivityRecord p = activities.get(srcPos);
+ ActivityRecord p = task.getChildAt(srcPos);
if (p.finishing) {
continue;
}
@@ -3389,8 +3381,7 @@
int replyChainEnd = -1;
final String taskAffinity = task.affinity;
- final ArrayList<ActivityRecord> activities = affinityTask.mActivities;
- final int numActivities = activities.size();
+ final int numActivities = task.getChildCount();
// Do not operate on or below the effective root Activity.
int lastActivityNdx = affinityTask.findRootIndex(true /* effectiveRoot */);
@@ -3398,7 +3389,7 @@
lastActivityNdx = 0;
}
for (int i = numActivities - 1; i > lastActivityNdx; --i) {
- ActivityRecord target = activities.get(i);
+ ActivityRecord target = task.getChildAt(i);
// TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
if (target.isRootOfTask()) break;
@@ -3435,7 +3426,7 @@
if (DEBUG_TASKS) Slog.v(TAG_TASKS,
"Finishing task at index " + start + " to " + i);
for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = activities.get(srcPos);
+ final ActivityRecord p = task.getChildAt(srcPos);
if (p.finishing) {
continue;
}
@@ -3443,7 +3434,7 @@
}
} else {
if (taskInsertionPoint < 0) {
- taskInsertionPoint = task.mActivities.size();
+ taskInsertionPoint = task.getChildCount();
}
@@ -3452,7 +3443,7 @@
"Reparenting from task=" + affinityTask + ":" + start + "-" + i
+ " to task=" + task + ":" + taskInsertionPoint);
for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = activities.get(srcPos);
+ final ActivityRecord p = task.getChildAt(srcPos);
p.reparent(task, taskInsertionPoint, "resetAffinityTaskIfNeededLocked");
if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
@@ -3589,9 +3580,9 @@
/** Finish all activities that were started for result from the specified activity. */
final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.resultTo == self && r.requestCode == requestCode) {
if ((r.resultWho == null && resultWho == null) ||
(r.resultWho != null && r.resultWho.equals(resultWho))) {
@@ -3637,11 +3628,11 @@
if (taskNdx < 0) {
break;
}
- activityNdx = mTaskHistory.get(taskNdx).mActivities.size() - 1;
+ activityNdx = mTaskHistory.get(taskNdx).getChildCount() - 1;
} while (activityNdx < 0);
}
if (activityNdx >= 0) {
- r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx);
+ r = mTaskHistory.get(taskNdx).getChildAt(activityNdx);
if (r.isState(STARTED, RESUMED, PAUSING, PAUSED)) {
if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) {
Slog.w(TAG, " Force finishing activity "
@@ -3659,8 +3650,8 @@
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
TaskRecord tr = mTaskHistory.get(taskNdx);
if (tr.voiceSession != null && tr.voiceSession.asBinder() == sessionBinder) {
- for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = tr.mActivities.get(activityNdx);
+ for (int activityNdx = tr.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = tr.getChildAt(activityNdx);
if (!r.finishing) {
r.finishIfPossible("finish-voice", false /* oomAdj */);
didOne = true;
@@ -3668,8 +3659,8 @@
}
} else {
// Check if any of the activities are using voice
- for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = tr.mActivities.get(activityNdx);
+ for (int activityNdx = tr.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = tr.getChildAt(activityNdx);
if (r.voiceSession != null && r.voiceSession.asBinder() == sessionBinder) {
// Inform of cancellation
r.clearVoiceSessionLocked();
@@ -3695,9 +3686,9 @@
void finishAllActivitiesImmediately() {
boolean noActivitiesInStack = true;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
noActivitiesInStack = false;
Slog.d(TAG, "finishAllActivitiesImmediatelyLocked: finishing " + r);
r.destroyIfPossible("finishAllActivitiesImmediatelyLocked");
@@ -3765,12 +3756,12 @@
return false;
}
int finishTo = start - 1;
- ActivityRecord parent = finishTo < 0 ? null : activities.get(finishTo);
+ ActivityRecord parent = finishTo < 0 ? null : task.getChildAt(finishTo);
boolean foundParentInTask = false;
final ComponentName dest = destIntent.getComponent();
if (start > 0 && dest != null) {
for (int i = finishTo; i >= 0; i--) {
- ActivityRecord r = activities.get(i);
+ ActivityRecord r = task.getChildAt(i);
if (r.info.packageName.equals(dest.getPackageName()) &&
r.info.name.equals(dest.getClassName())) {
finishTo = i;
@@ -3931,9 +3922,9 @@
boolean lastIsOpaque = false;
boolean activityRemoved = false;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -3978,15 +3969,14 @@
}
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Looking for activities to release in " + task);
int curNum = 0;
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
- final ActivityRecord activity = activities.get(actNdx);
+ for (int actNdx = 0; actNdx < task.getChildCount(); actNdx++) {
+ final ActivityRecord activity = task.getChildAt(actNdx);
if (activity.app == app && activity.isDestroyable()) {
if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity
+ " in state " + activity.getState() + " resumed=" + mResumedActivity
+ " pausing=" + mPausingActivity + " for reason " + reason);
activity.destroyImmediately(true /* removeFromApp */, reason);
- if (activities.get(actNdx) != activity) {
+ if (task.getChildAt(actNdx) != activity) {
// Was removed from list, back up so we don't miss the next one.
actNdx--;
}
@@ -4170,8 +4160,8 @@
if (timeTracker != null) {
// The caller wants a time tracker associated with this task.
- for (int i = tr.mActivities.size() - 1; i >= 0; i--) {
- tr.mActivities.get(i).appTimeTracker = timeTracker;
+ for (int i = tr.getChildCount() - 1; i >= 0; i--) {
+ tr.getChildAt(i).appTimeTracker = timeTracker;
}
}
@@ -4352,7 +4342,7 @@
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "stack.resize_" + mStackId);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + mStackId);
mService.deferWindowLayout();
try {
// Update override configurations of all tasks in the stack.
@@ -4378,7 +4368,7 @@
}
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -4425,9 +4415,9 @@
boolean willActivityBeVisibleLocked(IBinder token) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.appToken == token) {
return true;
}
@@ -4447,9 +4437,9 @@
void closeSystemDialogsLocked() {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) {
r.finishIfPossible("close-sys", true /* oomAdj */);
}
@@ -4556,10 +4546,10 @@
final int top = mTaskHistory.size() - 1;
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Performing unhandledBack(): top activity at " + top);
if (top >= 0) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
- int activityTop = activities.size() - 1;
+ final TaskRecord task = mTaskHistory.get(top);
+ int activityTop = task.getChildCount() - 1;
if (activityTop >= 0) {
- activities.get(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */);
+ task.getChildAt(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */);
}
}
}
@@ -4585,9 +4575,9 @@
void handleAppCrash(WindowProcessController app) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.app == app) {
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
@@ -4705,9 +4695,9 @@
// All activities that came from the package must be
// restarted as if there was a config change.
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord a = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord a = task.getChildAt(activityNdx);
if (a.info.packageName.equals(packageName)) {
a.forceNewConfig = true;
if (starting != null && a == starting && a.visible) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index f1284d81..ca74196 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -44,7 +44,7 @@
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.TYPE_VIRTUAL;
@@ -690,7 +690,7 @@
ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags,
int filterCallingUid) {
try {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resolveIntent");
int modifiedFlags = flags
| PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS;
if (intent.isWebIntent()
@@ -711,7 +711,7 @@
Binder.restoreCallingIdentity(token);
}
} finally {
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -824,7 +824,7 @@
System.identityHashCode(r), task.mTaskId, r.shortComponentName);
if (r.isActivityTypeHome()) {
// Home process is the root process of the task.
- updateHomeProcess(task.mActivities.get(0).app);
+ updateHomeProcess(task.getChildAt(0).app);
}
mService.getPackageManagerInternalLocked().notifyPackageUse(
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
@@ -1629,7 +1629,7 @@
mPendingTempOtherTaskInsetBounds = copyOrNull(tempOtherTaskInsetBounds);
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeDockedStack");
mService.deferWindowLayout();
try {
// Don't allow re-entry while resizing. E.g. due to docked stack detaching.
@@ -1695,7 +1695,7 @@
} finally {
mAllowDockedStackResize = true;
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -1717,7 +1717,7 @@
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizePinnedStack");
mService.deferWindowLayout();
try {
Rect insetBounds = null;
@@ -1739,7 +1739,7 @@
!DEFER_RESUME);
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -1796,6 +1796,7 @@
tr.removeTaskActivitiesLocked(reason);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
mService.getLockTaskController().clearLockedTask(tr);
+ mService.getTaskChangeNotificationController().notifyTaskStackChanged();
if (tr.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
@@ -1899,9 +1900,9 @@
task.createTask(onTop, true /* showForAllUsers */);
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
"Added restored task=" + task + " to stack=" + stack);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- activities.get(activityNdx).setTask(task);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
+ r.setTask(task);
}
return true;
}
@@ -2501,8 +2502,8 @@
return;
}
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
+ for (int i = task.getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.getChildAt(i);
if (r.attachedToProcess()) {
mMultiWindowModeChangedActivities.add(r);
}
@@ -2524,8 +2525,8 @@
}
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
+ for (int i = task.getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.getChildAt(i);
if (r.attachedToProcess()) {
mPipModeChangedActivities.add(r);
// If we are scheduling pip change, then remove this activity from multi-window
@@ -2543,8 +2544,8 @@
void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) {
mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
+ for (int i = task.getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.getChildAt(i);
if (r.attachedToProcess()) {
r.updatePictureInPictureMode(targetStackBounds, forceUpdate);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9397893..1a80006 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -770,13 +770,13 @@
boolean restrictedBgActivity = false;
if (!abort) {
try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
"shouldAbortBackgroundActivityStart");
restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
originatingPendingIntent, allowBackgroundActivityStart, intent);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -1401,41 +1401,12 @@
final ActivityStack startedActivityStack;
try {
mService.deferWindowLayout();
- result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
+ result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
} finally {
- final ActivityStack currentStack = r.getActivityStack();
- startedActivityStack = currentStack != null ? currentStack : mTargetStack;
-
- if (ActivityManager.isStartResultSuccessful(result)) {
- if (startedActivityStack != null) {
- // If there is no state change (e.g. a resumed activity is reparented to
- // top of another display) to trigger a visibility/configuration checking,
- // we have to update the configuration for changing to different display.
- final ActivityRecord currentTop =
- startedActivityStack.topRunningActivityLocked();
- if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
- mRootActivityContainer.ensureVisibilityAndConfig(
- currentTop, currentTop.getDisplayId(),
- true /* markFrozenIfConfigChanged */, false /* deferResume */);
- }
- }
- } else {
- // If we are not able to proceed, disassociate the activity from the task.
- // Leaving an activity in an incomplete state can lead to issues, such as
- // performing operations without a window container.
- final ActivityStack stack = mStartActivity.getActivityStack();
- if (stack != null) {
- mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
- }
-
- // Stack should also be detached from display and be removed if it's empty.
- if (startedActivityStack != null && startedActivityStack.isAttached()
- && startedActivityStack.numActivities() == 0
- && !startedActivityStack.isActivityTypeHome()) {
- startedActivityStack.remove();
- }
- }
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ startedActivityStack = handleStartResult(r, result);
mService.continueWindowLayout();
}
@@ -1445,6 +1416,49 @@
}
/**
+ * If the start result is success, ensure that the configuration of the started activity matches
+ * the current display. Otherwise clean up unassociated containers to avoid leakage.
+ *
+ * @return the stack where the successful started activity resides.
+ */
+ private @Nullable ActivityStack handleStartResult(@NonNull ActivityRecord started, int result) {
+ final ActivityStack currentStack = started.getActivityStack();
+ ActivityStack startedActivityStack = currentStack != null ? currentStack : mTargetStack;
+
+ if (ActivityManager.isStartResultSuccessful(result)) {
+ if (startedActivityStack != null) {
+ // If there is no state change (e.g. a resumed activity is reparented to top of
+ // another display) to trigger a visibility/configuration checking, we have to
+ // update the configuration for changing to different display.
+ final ActivityRecord currentTop = startedActivityStack.topRunningActivityLocked();
+ if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
+ mRootActivityContainer.ensureVisibilityAndConfig(
+ currentTop, currentTop.getDisplayId(),
+ true /* markFrozenIfConfigChanged */, false /* deferResume */);
+ }
+ }
+ return startedActivityStack;
+ }
+
+ // If we are not able to proceed, disassociate the activity from the task. Leaving an
+ // activity in an incomplete state can lead to issues, such as performing operations
+ // without a window container.
+ final ActivityStack stack = mStartActivity.getActivityStack();
+ if (stack != null) {
+ mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
+ }
+
+ // Stack should also be detached from display and be removed if it's empty.
+ if (startedActivityStack != null && startedActivityStack.isAttached()
+ && startedActivityStack.numActivities() == 0
+ && !startedActivityStack.isActivityTypeHome()) {
+ startedActivityStack.remove();
+ startedActivityStack = null;
+ }
+ return startedActivityStack;
+ }
+
+ /**
* Return true if background activity is really aborted.
*
* TODO(b/131748165): Refactor the logic so we don't need to call this method everywhere.
@@ -1469,7 +1483,7 @@
}
// Note: This method should only be called from {@link startActivity}.
- private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
+ private int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity, boolean restrictedBgActivity) {
@@ -1592,7 +1606,7 @@
// accordingly.
if (mTargetStack.isFocusable()
&& !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {
- mTargetStack.moveToFront("startActivityUnchecked");
+ mTargetStack.moveToFront("startActivityInner");
}
mRootActivityContainer.resumeFocusedStacksTopActivities(
mTargetStack, mStartActivity, mOptions);
@@ -1873,7 +1887,7 @@
mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
mLaunchFlags, mOptions);
mTargetStack.addTask(targetTask,
- !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
+ !mLaunchTaskBehind /* toTop */, "complyActivityFlags");
}
}
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) == 0 && !mAddingToTask
@@ -2430,7 +2444,7 @@
if (mStartActivity.getTaskRecord() == null || mStartActivity.getTaskRecord() == parent) {
parent.addActivityToTop(mStartActivity);
} else {
- mStartActivity.reparent(parent, parent.mActivities.size() /* top */, reason);
+ mStartActivity.reparent(parent, parent.getChildCount() /* top */, reason);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index ab35652..0488a3b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -159,10 +159,10 @@
*
* @param reasons A map from windowing mode to a reason integer why the transition was started,
* which must be one of the APP_TRANSITION_* values.
- * @param timestamp The time at which the app transition started in
- * {@link SystemClock#uptimeMillis()} timebase.
+ * @param timestampNs The time at which the app transition started in
+ * {@link SystemClock#elapsedRealtimeNs()} ()} timebase.
*/
- public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp);
+ public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestampNs);
/**
* Callback for window manager to let activity manager know that the app transition was
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 750fc68..20113a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -52,7 +52,7 @@
import static android.os.FactoryTest.FACTORY_TEST_OFF;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.SYSTEM_UID;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
@@ -63,7 +63,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -1607,6 +1606,7 @@
}
final long origId = Binder.clearCallingIdentity();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity");
try {
boolean res;
final boolean finishWithRootActivity =
@@ -1634,6 +1634,7 @@
}
return res;
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
@@ -1668,6 +1669,7 @@
try {
WindowProcessController proc = null;
synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityIdle");
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack == null) {
return;
@@ -1682,6 +1684,7 @@
}
}
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
@@ -1708,10 +1711,12 @@
public final void activityPaused(IBinder token) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityPaused");
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
stack.activityPausedLocked(token, false);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
Binder.restoreCallingIdentity(origId);
}
@@ -1732,6 +1737,7 @@
int restartingUid = 0;
final ActivityRecord r;
synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped");
r = ActivityRecord.isInStackLocked(token);
if (r != null) {
if (r.attachedToProcess()
@@ -1743,6 +1749,7 @@
}
r.activityStoppedLocked(icicle, persistentState, description);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (restartingName != null) {
@@ -1764,12 +1771,14 @@
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
synchronized (mGlobalLock) {
final long origId = Binder.clearCallingIdentity();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityDestroyed");
try {
final ActivityRecord activity = ActivityRecord.forTokenLocked(token);
if (activity != null) {
activity.destroyed("activityDestroyed");
}
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
@@ -1982,7 +1991,7 @@
final TaskRecord task = r.getTaskRecord();
int index = task.mActivities.lastIndexOf(r);
if (index > 0) {
- ActivityRecord under = task.mActivities.get(index - 1);
+ ActivityRecord under = task.getChildAt(index - 1);
under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
return r.setOccludesParent(false);
@@ -3321,29 +3330,6 @@
}
@Override
- public void startInPlaceAnimationOnFrontMostApplication(Bundle opts) {
- final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(opts);
- final ActivityOptions activityOptions = safeOptions != null
- ? safeOptions.getOptions(mStackSupervisor)
- : null;
- if (activityOptions == null
- || activityOptions.getAnimationType() != ActivityOptions.ANIM_CUSTOM_IN_PLACE
- || activityOptions.getCustomInPlaceResId() == 0) {
- throw new IllegalArgumentException("Expected in-place ActivityOption " +
- "with valid animation");
- }
- // Get top display of front most application.
- final ActivityStack focusedStack = getTopDisplayFocusedStack();
- if (focusedStack != null) {
- final DisplayContent dc = focusedStack.getDisplay().mDisplayContent;
- dc.prepareAppTransition(TRANSIT_TASK_IN_PLACE, false);
- dc.mAppTransition.overrideInPlaceAppTransition(activityOptions.getPackageName(),
- activityOptions.getCustomInPlaceResId());
- dc.executeAppTransition();
- }
- }
-
- @Override
public void removeStack(int stackId) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
synchronized (mGlobalLock) {
@@ -5530,8 +5516,8 @@
void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
String hostingType) {
try {
- if (Trace.isTagEnabled(TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dispatchingStartProcess:"
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"
+ activity.processName);
}
// Post message to start process to avoid possible deadlock of calling into AMS with the
@@ -5541,7 +5527,7 @@
isTop, hostingType, activity.intent.getComponent());
mH.sendMessage(m);
} finally {
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -5694,11 +5680,11 @@
private void updateResumedAppTrace(@Nullable ActivityRecord resumed) {
if (mTracedResumedActivity != null) {
- Trace.asyncTraceEnd(TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER,
constructResumedTraceName(mTracedResumedActivity.packageName), 0);
}
if (resumed != null) {
- Trace.asyncTraceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER,
constructResumedTraceName(resumed.packageName), 0);
}
mTracedResumedActivity = resumed;
@@ -6026,10 +6012,10 @@
@Override
public void notifyAppTransitionStarting(SparseIntArray reasons,
- long timestamp) {
+ long timestampNs) {
synchronized (mGlobalLock) {
mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
- reasons, timestamp);
+ reasons, timestampNs);
}
}
@@ -6797,7 +6783,14 @@
@Override
public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
synchronized (mGlobalLockWithoutBoost) {
- return mRootActivityContainer.attachApplication(wpc);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName);
+ }
+ try {
+ return mRootActivityContainer.attachApplication(wpc);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 93c461f..394b475 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -33,7 +33,6 @@
import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
@@ -2257,8 +2256,7 @@
static boolean isTaskTransit(int transit) {
return isTaskOpenTransit(transit)
|| transit == TRANSIT_TASK_CLOSE
- || transit == TRANSIT_TASK_TO_BACK
- || transit == TRANSIT_TASK_IN_PLACE;
+ || transit == TRANSIT_TASK_TO_BACK;
}
private static boolean isTaskOpenTransit(int transit) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 20a871b..0c07e15 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -31,7 +31,6 @@
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
@@ -179,8 +178,6 @@
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
try {
- processApplicationsAnimatingInPlace(transit);
-
handleClosingApps(transit, animLp, voiceInteraction);
handleOpeningApps(transit, animLp, voiceInteraction);
handleChangingApps(transit, animLp, voiceInteraction);
@@ -212,7 +209,7 @@
mDisplayContent.computeImeTarget(true /* updateImeTarget */);
mService.mAtmInternal.notifyAppTransitionStarting(mTempTransitionReasons.clone(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
if (transit == TRANSIT_SHOW_SINGLE_TASK_DISPLAY) {
mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
@@ -710,19 +707,4 @@
}
return topApp;
}
-
- private void processApplicationsAnimatingInPlace(int transit) {
- if (transit == TRANSIT_TASK_IN_PLACE) {
- // Find the focused window
- final WindowState win = mDisplayContent.findFocusedWindow();
- if (win != null) {
- final AppWindowToken wtoken = win.mAppToken;
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now animating app in place %s", wtoken);
- wtoken.cancelAnimation();
- wtoken.applyAnimationLocked(null, transit, false, false);
- wtoken.updateReportedVisibilityLocked();
- wtoken.showAllWindowsLocked();
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index a261341..e56fdd2 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -512,7 +512,7 @@
if (DEBUG_VISIBILITY) Slog.v(TAG, "VIS " + this + ": interesting="
+ numInteresting + " visible=" + numVisible);
if (nowDrawn != reportedDrawn) {
- onWindowsDrawn(nowDrawn, SystemClock.uptimeMillis());
+ onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());
reportedDrawn = nowDrawn;
}
if (nowVisible != reportedVisible) {
@@ -1420,7 +1420,7 @@
mReparenting = true;
getParent().removeChild(this);
- task.addChild(this, position);
+ task.addChild((ActivityRecord) this, position);
mReparenting = false;
@@ -2283,7 +2283,7 @@
}
}
- private final Runnable mAddStartingWindow = new Runnable() {
+ private class AddStartingWindow implements Runnable {
@Override
public void run() {
@@ -2343,7 +2343,9 @@
AppWindowToken.this);
}
}
- };
+ }
+
+ private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 8afbbdf..300ee1d 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -626,6 +626,10 @@
return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
}
+ boolean hasChild() {
+ return getChildCount() > 0;
+ }
+
abstract protected int getChildCount();
abstract protected E getChildAt(int index);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ac910cd..4c3611e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5152,7 +5152,7 @@
// Let surface flinger to set the display ID of this input window handle because we don't
// know which display the parent surface control is on.
final InputWindowHandle portalWindowHandle = new InputWindowHandle(
- null /* inputApplicationHandle */, null /* clientWindow */, INVALID_DISPLAY);
+ null /* inputApplicationHandle */, INVALID_DISPLAY);
portalWindowHandle.name = name;
portalWindowHandle.token = new Binder();
portalWindowHandle.layoutParamsFlags =
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7be4dbd..60e9819 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -16,13 +16,12 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.TYPE_BUILT_IN;
@@ -197,6 +196,11 @@
// Nav bar is never forced opaque.
private static final int NAV_BAR_FORCE_TRANSPARENT = 2;
+ /** Don't apply window animation (see {@link #selectAnimation}). */
+ static final int ANIMATION_NONE = -1;
+ /** Use the transit animation in style resource (see {@link #selectAnimation}). */
+ static final int ANIMATION_STYLEABLE = 0;
+
/**
* These are the system UI flags that, when changing, can cause the layout
* of the screen to change.
@@ -1037,9 +1041,9 @@
}
/**
- * Control the animation to run when a window's state changes. Return a
- * non-0 number to force the animation to a specific resource ID, or 0
- * to use the default animation.
+ * Control the animation to run when a window's state changes. Return a positive number to
+ * force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the
+ * style resource defining the animation, or {@link #ANIMATION_NONE} for no animation.
*
* @param win The window that is changing.
* @param transit What is happening to the window:
@@ -1048,9 +1052,9 @@
* {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_SHOW}, or
* {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_HIDE}.
*
- * @return Resource ID of the actual animation to use, or 0 for none.
+ * @return Resource ID of the actual animation to use, or {@link #ANIMATION_NONE} for none.
*/
- public int selectAnimationLw(WindowState win, int transit) {
+ int selectAnimation(WindowState win, int transit) {
if (DEBUG_ANIM) Slog.i(TAG, "selectAnimation in " + win
+ ": transit=" + transit);
if (win == mStatusBar) {
@@ -1058,7 +1062,7 @@
final boolean expanded = win.getAttrs().height == MATCH_PARENT
&& win.getAttrs().width == MATCH_PARENT;
if (isKeyguard || expanded) {
- return -1;
+ return ANIMATION_NONE;
}
if (transit == TRANSIT_EXIT
|| transit == TRANSIT_HIDE) {
@@ -1069,7 +1073,7 @@
}
} else if (win == mNavigationBar) {
if (win.getAttrs().windowAnimations != 0) {
- return 0;
+ return ANIMATION_STYLEABLE;
}
// This can be on either the bottom or the right or the left.
if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
@@ -1102,7 +1106,7 @@
}
}
} else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
- return selectDockedDividerAnimationLw(win, transit);
+ return selectDockedDividerAnimation(win, transit);
}
if (transit == TRANSIT_PREVIEW_DONE) {
@@ -1116,13 +1120,13 @@
// is shown. We don't want an animation on the dream, because
// we need it shown immediately with the keyguard animating away
// to reveal it.
- return -1;
+ return ANIMATION_NONE;
}
- return 0;
+ return ANIMATION_STYLEABLE;
}
- private int selectDockedDividerAnimationLw(WindowState win, int transit) {
+ private int selectDockedDividerAnimation(WindowState win, int transit) {
int insets = mDisplayContent.getDockedDividerController().getContentInsets();
// If the divider is behind the navigation bar, don't animate.
@@ -1141,14 +1145,14 @@
|| frame.bottom + insets >= win.getDisplayFrameLw().bottom);
final boolean offscreen = offscreenLandscape || offscreenPortrait;
if (behindNavBar || offscreen) {
- return 0;
+ return ANIMATION_STYLEABLE;
}
if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
return R.anim.fade_in;
} else if (transit == TRANSIT_EXIT) {
return R.anim.fade_out;
} else {
- return 0;
+ return ANIMATION_STYLEABLE;
}
}
@@ -3130,9 +3134,10 @@
final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
mService.getStackBounds(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
- mService.getStackBounds(
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
+ mService.getStackBounds(mDockedStackBounds.isEmpty()
+ ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
final Pair<Integer, Boolean> result =
updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
final int visibility = result.first;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 34820ac..abd7222 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -263,7 +263,7 @@
InputChannel[] channels = InputChannel.openInputChannelPair("drag");
mServerChannel = channels[0];
mClientChannel = channels[1];
- mService.mInputManager.registerInputChannel(mServerChannel, null);
+ mService.mInputManager.registerInputChannel(mServerChannel);
mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
mService.mH.getLooper(), mDragDropController);
@@ -272,7 +272,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.token = mServerChannel.getToken();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 7e085f6..bc95481 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -25,7 +25,7 @@
*/
class ImeInsetsSourceProvider extends InsetsSourceProvider {
- private WindowState mCurImeTarget;
+ private WindowState mImeTargetFromIme;
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
@@ -40,8 +40,8 @@
void onPostLayout() {
super.onPostLayout();
- if (mCurImeTarget != null
- && mCurImeTarget == mDisplayContent.mInputMethodTarget
+ if (mImeTargetFromIme != null
+ && isImeTargetFromDisplayContentAndImeSame()
&& mWin != null
&& mWin.isDrawnLw()
&& !mWin.mGivenInsetsPending) {
@@ -64,18 +64,33 @@
/**
* Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
* requests to show IME on {@param imeTarget}.
- * @param imeTarget imeTarget on which IME is displayed.
+ * @param imeTarget imeTarget on which IME request is coming from.
*/
void scheduleShowImePostLayout(WindowState imeTarget) {
- mCurImeTarget = imeTarget;
+ mImeTargetFromIme = imeTarget;
mShowImeRunner = () -> {
// Target should still be the same.
- if (mCurImeTarget == mDisplayContent.mInputMethodTarget) {
+ if (isImeTargetFromDisplayContentAndImeSame()) {
mDisplayContent.mInputMethodTarget.showInsets(
WindowInsets.Type.ime(), true /* fromIme */);
}
- mCurImeTarget = null;
+ mImeTargetFromIme = null;
};
}
+ private boolean isImeTargetFromDisplayContentAndImeSame() {
+ // IMMS#mLastImeTargetWindow always considers focused window as
+ // IME target, however DisplayContent#computeImeTarget() can compute
+ // a different IME target.
+ // Refer to WindowManagerService#applyImeVisibility(token, false).
+ // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window
+ // is above the parent, we will consider it as the same target for now.
+ // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
+ // actual IME target.
+ return mImeTargetFromIme == mDisplayContent.mInputMethodTarget
+ || (mDisplayContent.mInputMethodTarget.getParentWindow() == mImeTargetFromIme
+ && mDisplayContent.mInputMethodTarget.mSubLayer
+ > mImeTargetFromIme.mSubLayer);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index d774dc3..82ac6b8 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -189,7 +189,7 @@
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("ImmersiveModeConfirmation");
lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
lp.token = getWindowToken();
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 8dae016..ebe9f08 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -65,14 +65,14 @@
} else {
mClientChannel = channels[1];
}
- mService.mInputManager.registerInputChannel(mServerChannel, null);
+ mService.mInputManager.registerInputChannel(mServerChannel);
mApplicationHandle = new InputApplicationHandle(new Binder());
mApplicationHandle.name = name;
mApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mWindowHandle = new InputWindowHandle(mApplicationHandle, null, displayId);
+ mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
mWindowHandle.name = name;
mWindowHandle.token = mServerChannel.getToken();
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index ec36a82..9973e11 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -5,20 +5,25 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
import android.os.Debug;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Slog;
+import android.view.IWindow;
import android.view.KeyEvent;
import android.view.WindowManager;
import com.android.server.input.InputManagerService;
import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicReference;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
private final WindowManagerService mService;
// Set to true when the first input device configuration change notification
@@ -37,6 +42,13 @@
// which point the ActivityManager will enable dispatching.
private boolean mInputDispatchEnabled;
+ // TODO(b/141749603)) investigate if this can be part of client focus change dispatch
+ // Tracks the currently focused window used to update pointer capture state in clients
+ private AtomicReference<IWindow> mFocusedWindow = new AtomicReference<>();
+
+ // Tracks focused window pointer capture state
+ private boolean mFocusedWindowHasCapture;
+
public InputManagerCallback(WindowManagerService service) {
mService = service;
}
@@ -53,7 +65,7 @@
}
synchronized (mService.mGlobalLock) {
- WindowState windowState = mService.windowForClientLocked(null, token, false);
+ WindowState windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
Slog.i(TAG_WM, "WINDOW DIED " + windowState);
windowState.removeIfPossible();
@@ -72,9 +84,10 @@
AppWindowToken appWindowToken = null;
WindowState windowState = null;
boolean aboveSystem = false;
+ //TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
synchronized (mService.mGlobalLock) {
if (token != null) {
- windowState = mService.windowForClientLocked(null, token, false);
+ windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
appWindowToken = windowState.mAppToken;
}
@@ -109,7 +122,7 @@
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
final boolean abort = appWindowToken.keyDispatchingTimedOut(reason,
- (windowState != null) ? windowState.mSession.mPid : -1);
+ windowState.mSession.mPid);
if (!abort) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
@@ -239,6 +252,60 @@
mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
}
+ @Override
+ public boolean notifyFocusChanged(IBinder oldToken, IBinder newToken) {
+ boolean requestRefreshConfiguration = false;
+ final IWindow newFocusedWindow;
+ final WindowState win;
+
+ // TODO(b/141749603) investigate if this can be part of client focus change dispatch
+ synchronized (mService.mGlobalLock) {
+ win = mService.mInputToWindowMap.get(newToken);
+ }
+ newFocusedWindow = (win != null) ? win.mClient : null;
+
+ final IWindow focusedWindow = mFocusedWindow.get();
+ if (focusedWindow != null) {
+ if (newFocusedWindow != null
+ && newFocusedWindow.asBinder() == focusedWindow.asBinder()) {
+ Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
+ + focusedWindow);
+ return false;
+ }
+ requestRefreshConfiguration = dispatchPointerCaptureChanged(focusedWindow, false);
+ }
+ mFocusedWindow.set(newFocusedWindow);
+ return requestRefreshConfiguration;
+ }
+
+ @Override
+ public boolean requestPointerCapture(IBinder windowToken, boolean enabled) {
+ final IWindow focusedWindow = mFocusedWindow.get();
+ if (focusedWindow == null || focusedWindow.asBinder() != windowToken) {
+ Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
+ + windowToken);
+ return false;
+ }
+ if (mFocusedWindowHasCapture == enabled) {
+ Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
+ return false;
+ }
+ return dispatchPointerCaptureChanged(focusedWindow, enabled);
+ }
+
+ private boolean dispatchPointerCaptureChanged(IWindow focusedWindow, boolean enabled) {
+ if (mFocusedWindowHasCapture != enabled) {
+ mFocusedWindowHasCapture = enabled;
+ try {
+ focusedWindow.dispatchPointerCaptureChanged(enabled);
+ } catch (RemoteException ex) {
+ /* ignore */
+ }
+ return true;
+ }
+ return false;
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 932b4fa..584c6e1 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -112,7 +112,7 @@
}
}
- private final Runnable mUpdateInputWindows = new Runnable() {
+ private class UpdateInputWindows implements Runnable {
@Override
public void run() {
synchronized (mService.mGlobalLock) {
@@ -148,7 +148,9 @@
mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
}
}
- };
+ }
+
+ private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows();
public InputMonitor(WindowManagerService service, int displayId) {
mService = service;
@@ -411,7 +413,7 @@
WallpaperController wallpaperController;
// An invalid window handle that tells SurfaceFlinger not update the input info.
- final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
+ final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, mDisplayId);
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index b7184a5..22ba82a 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -33,4 +33,13 @@
*/
default void showInsets(@InsetType int types, boolean fromIme) {
}
+
+ /**
+ * Instructs the control target to hide inset sources.
+ *
+ * @param types to specify which types of insets source window should be hidden.
+ * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
+ */
+ default void hideInsets(@InsetType int types, boolean fromIme) {
+ }
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index cc55e01..3731d3f 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -241,6 +241,10 @@
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
OnAnimationFinishedCallback finishCallback) {
+ // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
+ t.setAlpha(animationLeash, 1 /* alpha */);
+ t.hide(animationLeash);
+
mCapturedLeash = animationLeash;
final Rect frame = mWin.getWindowFrames().mFrame;
t.setPosition(mCapturedLeash, frame.left, frame.top);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 2b5eb3a..52cc422 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
@@ -180,7 +180,7 @@
if (!mKeyguardShowing) {
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "keyguardGoingAway");
mService.deferWindowLayout();
try {
setKeyguardGoingAway(true);
@@ -202,11 +202,8 @@
true /* taskSwitch */);
mWindowManager.executeAppTransition();
} finally {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
index 93e2d8d..362ed3c 100644
--- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -70,9 +70,12 @@
}
@Override
- public void onIntentStarted(Intent intent) {
+ public void onIntentStarted(Intent intent, long timestampNs) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- LaunchObserverRegistryImpl::handleOnIntentStarted, this, intent));
+ LaunchObserverRegistryImpl::handleOnIntentStarted,
+ this,
+ intent,
+ timestampNs));
}
@Override
@@ -99,9 +102,22 @@
@Override
public void onActivityLaunchFinished(
- @ActivityRecordProto byte[] activity) {
+ @ActivityRecordProto byte[] activity,
+ long timestampNs) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, this, activity));
+ LaunchObserverRegistryImpl::handleOnActivityLaunchFinished,
+ this,
+ activity,
+ timestampNs));
+ }
+
+ @Override
+ public void onReportFullyDrawn(@ActivityRecordProto byte[] activity, long timestampNs) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ LaunchObserverRegistryImpl::handleOnReportFullyDrawn,
+ this,
+ activity,
+ timestampNs));
}
// Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be
@@ -116,11 +132,11 @@
mList.remove(observer);
}
- private void handleOnIntentStarted(Intent intent) {
+ private void handleOnIntentStarted(Intent intent, long timestampNs) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
ActivityMetricsLaunchObserver o = mList.get(i);
- o.onIntentStarted(intent);
+ o.onIntentStarted(intent, timestampNs);
}
}
@@ -152,11 +168,20 @@
}
private void handleOnActivityLaunchFinished(
- @ActivityRecordProto byte[] activity) {
+ @ActivityRecordProto byte[] activity, long timestampNs) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
ActivityMetricsLaunchObserver o = mList.get(i);
- o.onActivityLaunchFinished(activity);
+ o.onActivityLaunchFinished(activity, timestampNs);
+ }
+ }
+
+ private void handleOnReportFullyDrawn(
+ @ActivityRecordProto byte[] activity, long timestampNs) {
+ // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+ for (int i = 0; i < mList.size(); i++) {
+ ActivityMetricsLaunchObserver o = mList.get(i);
+ o.onReportFullyDrawn(activity, timestampNs);
}
}
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 94d010e..c59a73b 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -20,7 +20,7 @@
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Binder;
+import android.os.IBinder;
import android.os.Process;
import android.view.InputChannel;
import android.view.InputEventReceiver;
@@ -170,7 +170,7 @@
final InputWindowHandle mWindowHandle;
final InputEventReceiver mInputEventReceiver;
final WindowManagerService mWmService;
- final Binder mToken = new Binder();
+ final IBinder mToken;
InputInterceptor(String namePrefix, WindowState win) {
mWmService = win.mWmService;
@@ -180,10 +180,11 @@
mClientChannel = channels[1];
mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
- mWmService.mInputManager.registerInputChannel(mServerChannel, mToken);
+ mWmService.mInputManager.registerInputChannel(mServerChannel);
+ mToken = mServerChannel.getToken();
mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */,
- null /* clientWindow */, win.getDisplayId());
+ win.getDisplayId());
mWindowHandle.name = name;
mWindowHandle.token = mToken;
mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 062cdc5..12579e6 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -22,7 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.TRANSIT_NONE;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -158,7 +158,7 @@
void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
// TODO(multi-display) currently only support recents animation in default display.
final DisplayContent dc =
@@ -263,7 +263,7 @@
throw e;
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -297,7 +297,7 @@
}
mWindowManager.inSurfaceTransaction(() -> {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
mService.deferWindowLayout();
try {
@@ -394,7 +394,7 @@
throw e;
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
});
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index bd27905..d606e5d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -98,8 +98,10 @@
private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
new ArrayList<>();
private final int mDisplayId;
- private final Runnable mFailsafeRunnable = () ->
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
+ private boolean mWillFinishToHome = false;
+ private final Runnable mFailsafeRunnable = () -> cancelAnimation(
+ mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
+ "failSafeRunnable");
// The recents component app token that is shown behind the visibile tasks
private AppWindowToken mTargetAppToken;
@@ -326,6 +328,13 @@
}
}
}
+
+ @Override
+ public void setWillFinishToHome(boolean willFinishToHome) {
+ synchronized (mService.getWindowManagerLock()) {
+ mWillFinishToHome = willFinishToHome;
+ }
+ }
};
/**
@@ -494,7 +503,8 @@
}
final SparseIntArray reasons = new SparseIntArray();
reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
- mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
+ mService.mAtmInternal
+ .notifyAppTransitionStarting(reasons, SystemClock.elapsedRealtimeNanos());
}
private RemoteAnimationTarget[] createAppAnimations() {
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index d78d517..60833c3 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -983,7 +983,7 @@
stack.resize(task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, !DEFER_RESUME);
- if (task.mActivities.size() == 1) {
+ if (task.getChildCount() == 1) {
// Defer resume until below, and do not schedule PiP changes until we animate below
task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
false /* schedulePictureInPictureModeChange */, reason);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a181c18..2657826 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -58,7 +58,7 @@
import java.io.PrintWriter;
import java.util.function.Consumer;
-class Task extends WindowContainer<AppWindowToken> implements ConfigurationContainerListener{
+class Task extends WindowContainer<ActivityRecord> implements ConfigurationContainerListener{
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
// TODO: Track parent marks like this in WindowContainer.
@@ -120,9 +120,12 @@
// TODO: Remove after unification.
@Override
- public void onConfigurationChanged(Configuration newParentConfig) {
- // Only forward configuration changes in cases where children won't get it from TaskRecord.
- onConfigurationChanged(newParentConfig, mTaskRecord == null /*forwardToChildren*/);
+ public void onConfigurationChanged(Configuration newParentConfig, boolean forwardToChildren) {
+ // Forward configuration changes in cases
+ // - children won't get it from TaskRecord
+ // - it's a pinned task
+ forwardToChildren &= (mTaskRecord == null) || inPinnedWindowingMode();
+ super.onConfigurationChanged(newParentConfig, forwardToChildren);
}
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
@@ -170,14 +173,14 @@
}
@Override
- void addChild(AppWindowToken wtoken, int position) {
+ void addChild(ActivityRecord child, int position) {
position = getAdjustedAddPosition(position);
- super.addChild(wtoken, position);
+ super.addChild(child, position);
mDeferRemoval = false;
}
@Override
- void positionChildAt(int position, AppWindowToken child, boolean includingParents) {
+ void positionChildAt(int position, ActivityRecord child, boolean includingParents) {
position = getAdjustedAddPosition(position);
super.positionChildAt(position, child, includingParents);
mDeferRemoval = false;
@@ -279,13 +282,13 @@
}
@Override
- void removeChild(AppWindowToken token) {
- if (!mChildren.contains(token)) {
+ void removeChild(ActivityRecord child) {
+ if (!mChildren.contains(child)) {
Slog.e(TAG, "removeChild: token=" + this + " not found.");
return;
}
- super.removeChild(token);
+ super.removeChild(child);
if (mChildren.isEmpty()) {
EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
@@ -674,18 +677,18 @@
return null;
}
- void positionChildAtTop(AppWindowToken aToken) {
- positionChildAt(aToken, POSITION_TOP);
+ void positionChildAtTop(ActivityRecord child) {
+ positionChildAt(child, POSITION_TOP);
}
- void positionChildAt(AppWindowToken aToken, int position) {
- if (aToken == null) {
+ void positionChildAt(ActivityRecord child, int position) {
+ if (child == null) {
Slog.w(TAG_WM,
"Attempted to position of non-existing app");
return;
}
- positionChildAt(position, aToken, false /* includeParents */);
+ positionChildAt(position, child, false /* includeParents */);
}
void forceWindowsScaleable(boolean force) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index b680fa4..478b1b5 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -256,7 +256,7 @@
final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
mServerChannel = channels[0];
mClientChannel = channels[1];
- mService.mInputManager.registerInputChannel(mServerChannel, null);
+ mService.mInputManager.registerInputChannel(mServerChannel);
mInputEventReceiver = new WindowPositionerEventReceiver(
mClientChannel, mService.mAnimationHandler.getLooper(),
@@ -267,8 +267,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
- display.getDisplayId());
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, display.getDisplayId());
mDragWindowHandle.name = TAG;
mDragWindowHandle.token = mServerChannel.getToken();
mDragWindowHandle.layer = mService.getDragLayerLocked();
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 75333c7..299b32c 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -48,7 +48,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -447,7 +447,7 @@
}
void cleanUpResourcesForDestroy() {
- if (!mActivities.isEmpty()) {
+ if (hasChild()) {
return;
}
@@ -553,7 +553,7 @@
// This method assumes that the task is already placed in the right stack.
// we do not mess with that decision and we only do the resize!
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + mTaskId);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeTask_" + mTaskId);
boolean updatedConfig = false;
mTmpConfig.setTo(getResolvedOverrideConfiguration());
@@ -587,7 +587,7 @@
saveLaunchingStateIfNeeded();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return kept;
} finally {
mAtmService.continueWindowLayout();
@@ -1095,7 +1095,7 @@
// There are no non-finishing activities in the task.
return null;
}
- return mActivities.get(rootActivityIndex);
+ return getChildAt(rootActivityIndex);
}
ActivityRecord getTopActivity() {
@@ -1103,8 +1103,8 @@
}
ActivityRecord getTopActivity(boolean includeOverlays) {
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.finishing || (!includeOverlays && r.mTaskOverlay)) {
continue;
}
@@ -1115,8 +1115,8 @@
ActivityRecord topRunningActivityLocked() {
if (mStack != null) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = getChildAt(activityNdx);
if (!r.finishing && r.okToShowLocked()) {
return r;
}
@@ -1126,8 +1126,8 @@
}
boolean isVisible() {
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.visible) {
return true;
}
@@ -1139,8 +1139,8 @@
* Return true if any activities in this task belongs to input uid.
*/
boolean containsAppUid(int uid) {
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.getUid() == uid) {
return true;
}
@@ -1150,8 +1150,8 @@
void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
if (mStack != null) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = getChildAt(activityNdx);
if (!r.finishing && r.okToShowLocked() && r.visibleIgnoringKeyguard) {
outActivities.add(r);
}
@@ -1161,8 +1161,8 @@
ActivityRecord topRunningActivityWithStartingWindowLocked() {
if (mStack != null) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = getChildAt(activityNdx);
if (r.mStartingWindowState != STARTING_WINDOW_SHOWN
|| r.finishing || !r.okToShowLocked()) {
continue;
@@ -1179,8 +1179,8 @@
*/
void getNumRunningActivities(TaskActivitiesReport reportOut) {
reportOut.reset();
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.finishing) {
continue;
}
@@ -1227,17 +1227,17 @@
}
void addActivityToTop(ActivityRecord r) {
- addActivityAtIndex(mActivities.size(), r);
+ addActivityAtIndex(getChildCount(), r);
}
@Override
/*@WindowConfiguration.ActivityType*/
public int getActivityType() {
final int applicationType = super.getActivityType();
- if (applicationType != ACTIVITY_TYPE_UNDEFINED || mActivities.isEmpty()) {
+ if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
return applicationType;
}
- return mActivities.get(0).getActivityType();
+ return getChildAt(0).getActivityType();
}
/**
@@ -1259,7 +1259,7 @@
numFullscreen++;
}
// Only set this based on the first activity
- if (mActivities.isEmpty()) {
+ if (!hasChild()) {
if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) {
// Normally non-standard activity type for the activity record will be set when the
// object is created, however we delay setting the standard application type until
@@ -1279,10 +1279,10 @@
r.setActivityType(getActivityType());
}
- final int size = mActivities.size();
+ final int size = getChildCount();
if (index == size && size > 0) {
- final ActivityRecord top = mActivities.get(size - 1);
+ final ActivityRecord top = getChildAt(size - 1);
if (top.mTaskOverlay) {
// Place below the task overlay activity since the overlay activity should always
// be on top.
@@ -1341,7 +1341,7 @@
mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
}
- if (mActivities.isEmpty()) {
+ if (!hasChild()) {
return !mReuseTask;
}
updateEffectiveIntent();
@@ -1355,8 +1355,8 @@
*/
boolean onlyHasTaskOverlayActivities(boolean excludeFinishing) {
int count = 0;
- for (int i = mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = getChildAt(i);
if (excludeFinishing && r.finishing) {
continue;
}
@@ -1372,7 +1372,7 @@
// We will automatically remove the task either if it has explicitly asked for
// this, or it is empty and has never contained an activity that got shown to
// the user.
- return autoRemoveRecents || (mActivities.isEmpty() && !hasBeenVisible);
+ return autoRemoveRecents || (!hasChild() && !hasBeenVisible);
}
/**
@@ -1380,9 +1380,9 @@
* task starting at a specified index.
*/
final void performClearTaskAtIndexLocked(int activityNdx, String reason) {
- int numActivities = mActivities.size();
+ int numActivities = getChildCount();
for ( ; activityNdx < numActivities; ++activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1429,9 +1429,9 @@
* or null if none was found.
*/
final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) {
- int numActivities = mActivities.size();
+ int numActivities = getChildCount();
for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ ActivityRecord r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1440,7 +1440,7 @@
final ActivityRecord ret = r;
for (++activityNdx; activityNdx < numActivities; ++activityNdx) {
- r = mActivities.get(activityNdx);
+ r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1591,8 +1591,8 @@
*/
final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) {
final ComponentName realActivity = r.mActivityComponent;
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord candidate = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord candidate = getChildAt(activityNdx);
if (candidate.finishing) {
continue;
}
@@ -1609,12 +1609,12 @@
// Traverse upwards looking for any break between main task activities and
// utility activities.
int activityNdx;
- final int numActivities = mActivities.size();
+ final int numActivities = getChildCount();
final boolean relinquish = numActivities != 0 &&
- (mActivities.get(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
+ (getChildAt(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
++activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (relinquish && (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
// This will be the top activity for determining taskDescription. Pre-inc to
// overcome initial decrement below.
@@ -1642,7 +1642,7 @@
boolean navigationBarContrastWhenTransparent = false;
boolean topActivity = true;
for (--activityNdx; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.mTaskOverlay) {
continue;
}
@@ -1697,9 +1697,9 @@
*/
int findRootIndex(boolean effectiveRoot) {
int effectiveNdx = -1;
- final int topActivityNdx = mActivities.size() - 1;
+ final int topActivityNdx = getChildCount() - 1;
for (int activityNdx = 0; activityNdx <= topActivityNdx; ++activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1720,7 +1720,7 @@
// But we still want to update the intent, so let's use the bottom activity.
effectiveRootIndex = 0;
}
- final ActivityRecord r = mActivities.get(effectiveRootIndex);
+ final ActivityRecord r = getChildAt(effectiveRootIndex);
setIntent(r);
// Update the task description when the activities change
@@ -2289,8 +2289,8 @@
}
void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.visible) {
r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch);
}
@@ -2462,7 +2462,7 @@
sb.append(" StackId=");
sb.append(getStackId());
sb.append(" sz=");
- sb.append(mActivities.size());
+ sb.append(getChildCount());
sb.append('}');
return sb.toString();
}
@@ -2495,8 +2495,8 @@
final long token = proto.start(fieldId);
super.writeToProto(proto, CONFIGURATION_CONTAINER, logLevel);
proto.write(ID, mTaskId);
- for (int i = mActivities.size() - 1; i >= 0; i--) {
- ActivityRecord activity = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ ActivityRecord activity = getChildAt(i);
activity.writeToProto(proto, ACTIVITIES);
}
proto.write(STACK_ID, mStack.mStackId);
@@ -2607,10 +2607,9 @@
out.endTag(null, TAG_INTENT);
}
- final ArrayList<ActivityRecord> activities = mActivities;
- final int numActivities = activities.size();
+ final int numActivities = getChildCount();
for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT
| FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) &&
diff --git a/services/core/java/com/android/server/wm/WindowHashMap.java b/services/core/java/com/android/server/wm/WindowHashMap.java
deleted file mode 100644
index 49bba41..0000000
--- a/services/core/java/com/android/server/wm/WindowHashMap.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.os.IBinder;
-
-import java.util.HashMap;
-
-/**
- * Subclass of HashMap such that we can instruct the compiler to boost our thread priority when
- * locking this class. See makefile.
- */
-class WindowHashMap extends HashMap<IBinder, WindowState> {
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index f4b7672..0cb4826 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -514,6 +514,13 @@
public abstract void showImePostLayout(IBinder imeTargetWindowToken);
/**
+ * Hide IME using imeTargetWindow when requested.
+ *
+ * @param displayId on which IME is shown
+ */
+ public abstract void hideIme(int displayId);
+
+ /**
* Tell window manager about a package that should not be running with high refresh rate
* setting until removeNonHighRefreshRatePackage is called for the same package.
*
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6f9f2c0..c485280 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -236,6 +236,7 @@
import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowContentFrameStats;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.RemoveContentMode;
@@ -518,7 +519,10 @@
final ArraySet<Session> mSessions = new ArraySet<>();
/** Mapping from an IWindow IBinder to the server's Window object. */
- final WindowHashMap mWindowMap = new WindowHashMap();
+ final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
+
+ /** Mapping from an InputWindowHandle token to the server's Window object. */
+ final HashMap<IBinder, WindowState> mInputToWindowMap = new HashMap<>();
/** Global service lock used by the package the owns this service. */
final WindowManagerGlobalLock mGlobalLock;
@@ -1578,7 +1582,6 @@
win.attach();
mWindowMap.put(client.asBinder(), win);
-
win.initAppOpsState();
final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),
@@ -7310,6 +7313,16 @@
}
@Override
+ public void hideIme(int displayId) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc != null && dc.mInputMethodTarget != null) {
+ dc.mInputMethodTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */);
+ }
+ }
+ }
+
+ @Override
public boolean isUidAllowedOnDisplay(int displayId, int uid) {
if (displayId == Display.DEFAULT_DISPLAY) {
return true;
@@ -7592,7 +7605,7 @@
}
private void onPointerDownOutsideFocusLocked(IBinder touchedToken) {
- final WindowState touchedWindow = windowForClientLocked(null, touchedToken, false);
+ final WindowState touchedWindow = mInputToWindowMap.get(touchedToken);
if (touchedWindow == null || !touchedWindow.canReceiveKeys()) {
return;
}
@@ -7665,9 +7678,9 @@
clientChannel.transferTo(outInputChannel);
clientChannel.dispose();
- mInputManager.registerInputChannel(inputChannel, null /* generate new token */);
+ mInputManager.registerInputChannel(inputChannel);
- InputWindowHandle h = new InputWindowHandle(null, null, displayId);
+ InputWindowHandle h = new InputWindowHandle(null, displayId);
h.token = inputChannel.getToken();
h.name = name;
h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56e08b2..7ff9b70 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -820,8 +820,7 @@
mLastRequestedHeight = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
- mAppToken != null ? mAppToken.mInputApplicationHandle : null, c,
- getDisplayId());
+ mAppToken != null ? mAppToken.mInputApplicationHandle : null, getDisplayId());
}
void attach() {
@@ -2191,7 +2190,9 @@
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
- mInputWindowHandle.token = mClient.asBinder();
+ mWmService.mInputManager.registerInputChannel(mInputChannel);
+ mClientChannel.setToken(mInputChannel.getToken());
+ mInputWindowHandle.token = mInputChannel.getToken();
if (outInputChannel != null) {
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
@@ -2202,7 +2203,7 @@
// Create dummy event receiver that simply reports all events as handled.
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
}
- mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
+ mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
}
void disposeInputChannel() {
@@ -2223,6 +2224,7 @@
mClientChannel = null;
}
mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
+ mWmService.mInputToWindowMap.remove(mInputWindowHandle.token);
mInputWindowHandle.token = null;
}
@@ -3375,6 +3377,15 @@
}
}
+ @Override
+ public void hideInsets(@InsetType int types, boolean fromIme) {
+ try {
+ mClient.hideInsets(types, fromIme);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver showInsets", e);
+ }
+ }
+
Rect getBackdropFrame(Rect frame) {
// When the task is docked, we send fullscreen sized backDropFrame as soon as resizing
// start even if we haven't received the relayout window, so that the client requests
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1328273..eac372f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1348,13 +1348,16 @@
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
// is running.
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#applyAnimationLocked");
if (mWin.mToken.okToAnimate()) {
- int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimationLw(mWin, transit);
+ int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);
int attr = -1;
Animation a = null;
- if (anim != 0) {
- a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
+ if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {
+ if (anim != DisplayPolicy.ANIMATION_NONE) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#loadAnimation");
+ a = AnimationUtils.loadAnimation(mContext, anim);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
} else {
switch (transit) {
case WindowManagerPolicy.TRANSIT_ENTER:
@@ -1384,7 +1387,9 @@
+ " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
mWin.startAnimation(a);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
} else {
@@ -1395,7 +1400,6 @@
mWin.getDisplayContent().adjustForImeIfNeeded();
}
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
return mWin.isAnimating();
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 56f6d4b..0cfdebc 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -23,7 +23,6 @@
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
import android.os.Debug;
-import android.os.Trace;
import android.util.Slog;
import java.io.PrintWriter;
@@ -52,15 +51,19 @@
/** The number of layout requests when deferring. */
private int mDeferredRequests;
- private final Runnable mPerformSurfacePlacement;
-
- public WindowSurfacePlacer(WindowManagerService service) {
- mService = service;
- mPerformSurfacePlacement = () -> {
+ private class Traverser implements Runnable {
+ @Override
+ public void run() {
synchronized (mService.mGlobalLock) {
performSurfacePlacement();
}
- };
+ }
+ }
+
+ private final Traverser mPerformSurfacePlacement = new Traverser();
+
+ WindowSurfacePlacer(WindowManagerService service) {
+ mService = service;
}
/**
@@ -152,7 +155,6 @@
return;
}
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
mInLayout = true;
boolean recoveringMemory = false;
@@ -198,8 +200,6 @@
mInLayout = false;
Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
}
-
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
void debugLayoutRepeats(final String msg, int pendingLayoutChanges) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index fb2fdab..dd2629d 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -201,8 +201,7 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
- status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
- int32_t displayId);
+ status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
status_t registerInputMonitor(JNIEnv* env, const sp<InputChannel>& inputChannel,
int32_t displayId, bool isGestureMonitor);
status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
@@ -435,10 +434,9 @@
}
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
- const sp<InputChannel>& inputChannel, int32_t displayId) {
+ const sp<InputChannel>& inputChannel) {
ATRACE_CALL();
- return mInputManager->getDispatcher()->registerInputChannel(
- inputChannel, displayId);
+ return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
}
status_t NativeInputManager::registerInputMonitor(JNIEnv* /* env */,
@@ -1405,7 +1403,7 @@
}
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject inputChannelObj, jint displayId) {
+ jlong ptr, jobject inputChannelObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
@@ -1415,7 +1413,7 @@
return;
}
- status_t status = im->registerInputChannel(env, inputChannel, displayId);
+ status_t status = im->registerInputChannel(env, inputChannel);
if (status) {
std::string message;
@@ -1757,7 +1755,7 @@
{ "nativeHasKeys", "(JII[I[Z)Z",
(void*) nativeHasKeys },
{ "nativeRegisterInputChannel",
- "(JLandroid/view/InputChannel;I)V",
+ "(JLandroid/view/InputChannel;)V",
(void*) nativeRegisterInputChannel },
{ "nativeRegisterInputMonitor",
"(JLandroid/view/InputChannel;IZ)V",
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 9ceb770..906b568 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -17,6 +17,8 @@
#define LOG_TAG "VerityUtils"
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
#include "jni.h"
#include <utils/Log.h>
@@ -72,11 +74,17 @@
namespace {
int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) {
- const char* path = env->GetStringUTFChars(filePath, nullptr);
- ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
- env->ReleaseStringUTFChars(filePath, path);
+ ScopedUtfChars path(env, filePath);
+ if (path.c_str() == nullptr) {
+ return EINVAL;
+ }
+ ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
if (rfd.get() < 0) {
- return errno;
+ return errno;
+ }
+ ScopedByteArrayRO signature_bytes(env, signature);
+ if (signature_bytes.get() == nullptr) {
+ return EINVAL;
}
fsverity_enable_arg arg = {};
@@ -85,11 +93,11 @@
arg.block_size = 4096;
arg.salt_size = 0;
arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
- arg.sig_size = env->GetArrayLength(signature);
- arg.sig_ptr = reinterpret_cast<uintptr_t>(signature);
+ arg.sig_size = signature_bytes.size();
+ arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
- return errno;
+ return errno;
}
return 0;
}
@@ -101,14 +109,16 @@
fsverity_digest *data = reinterpret_cast<fsverity_digest *>(&bytes);
data->digest_size = kSha256Bytes; // the only input/output parameter
- const char* path = env->GetStringUTFChars(filePath, nullptr);
- ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
- env->ReleaseStringUTFChars(filePath, path);
+ ScopedUtfChars path(env, filePath);
+ if (path.c_str() == nullptr) {
+ return EINVAL;
+ }
+ ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
if (rfd.get() < 0) {
- return errno;
+ return errno;
}
if (ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) {
- return errno;
+ return errno;
}
return 0;
}
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 47790ce..91c05a8 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -5,4 +5,13 @@
libs: [
"services.core",
],
+
+ plugins: [
+ "compat-changeid-annotation-processor",
+ ],
}
+
+platform_compat_config {
+ name: "services-devicepolicy-platform-compat-config",
+ src: ":services.devicepolicy",
+}
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9569ac8..6f643c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -129,6 +129,7 @@
import android.app.admin.DeviceStateCache;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.StartInstallingUpdateCallback;
@@ -137,6 +138,8 @@
import android.app.backup.IBackupManager;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -235,6 +238,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -251,6 +255,8 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
@@ -494,6 +500,22 @@
private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
+ /**
+ * For admin apps targeting R+, throw when the app sets password requirement
+ * that is not taken into account at given quality. For example when quality is set
+ * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't make sense to
+ * require certain password length. If the intent is to require a password of certain length
+ * having at least NUMERIC quality, the admin should first call
+ * {@link #setPasswordQuality(ComponentName, int, boolean)} and only then call
+ * {@link #setPasswordMinimumLength(ComponentName, int, boolean)}.
+ *
+ * <p>Conversely when an admin app targeting R+ lowers password quality, those
+ * requirements that stop making sense are reset to default values.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L;
+
final Context mContext;
final Injector mInjector;
final IPackageManager mIPackageManager;
@@ -506,6 +528,7 @@
private final LockSettingsInternal mLockSettingsInternal;
private final DeviceAdminServiceController mDeviceAdminServiceController;
private final OverlayPackagesProvider mOverlayPackagesProvider;
+ private final IPlatformCompat mIPlatformCompat;
private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
@@ -968,19 +991,8 @@
static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
- static final int DEF_MINIMUM_PASSWORD_LENGTH = 0;
- static final int DEF_MINIMUM_PASSWORD_LETTERS = 1;
- static final int DEF_MINIMUM_PASSWORD_UPPER_CASE = 0;
- static final int DEF_MINIMUM_PASSWORD_LOWER_CASE = 0;
- static final int DEF_MINIMUM_PASSWORD_NUMERIC = 1;
- static final int DEF_MINIMUM_PASSWORD_SYMBOLS = 1;
- static final int DEF_MINIMUM_PASSWORD_NON_LETTER = 0;
@NonNull
- PasswordMetrics minimumPasswordMetrics = new PasswordMetrics(
- PASSWORD_QUALITY_UNSPECIFIED, DEF_MINIMUM_PASSWORD_LENGTH,
- DEF_MINIMUM_PASSWORD_LETTERS, DEF_MINIMUM_PASSWORD_UPPER_CASE,
- DEF_MINIMUM_PASSWORD_LOWER_CASE, DEF_MINIMUM_PASSWORD_NUMERIC,
- DEF_MINIMUM_PASSWORD_SYMBOLS, DEF_MINIMUM_PASSWORD_NON_LETTER);
+ PasswordPolicy mPasswordPolicy = new PasswordPolicy();
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
@@ -1115,36 +1127,36 @@
out.startTag(null, TAG_POLICIES);
info.writePoliciesToXml(out);
out.endTag(null, TAG_POLICIES);
- if (minimumPasswordMetrics.quality != PASSWORD_QUALITY_UNSPECIFIED) {
+ if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
writeAttributeValueToXml(
- out, TAG_PASSWORD_QUALITY, minimumPasswordMetrics.quality);
- if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) {
+ out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
+ if (mPasswordPolicy.length != PasswordPolicy.DEF_MINIMUM_LENGTH) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LENGTH, minimumPasswordMetrics.length);
+ out, TAG_MIN_PASSWORD_LENGTH, mPasswordPolicy.length);
}
- if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
+ if (mPasswordPolicy.upperCase != PasswordPolicy.DEF_MINIMUM_UPPER_CASE) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_UPPERCASE, minimumPasswordMetrics.upperCase);
+ out, TAG_MIN_PASSWORD_UPPERCASE, mPasswordPolicy.upperCase);
}
- if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
+ if (mPasswordPolicy.lowerCase != PasswordPolicy.DEF_MINIMUM_LOWER_CASE) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LOWERCASE, minimumPasswordMetrics.lowerCase);
+ out, TAG_MIN_PASSWORD_LOWERCASE, mPasswordPolicy.lowerCase);
}
- if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) {
+ if (mPasswordPolicy.letters != PasswordPolicy.DEF_MINIMUM_LETTERS) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LETTERS, minimumPasswordMetrics.letters);
+ out, TAG_MIN_PASSWORD_LETTERS, mPasswordPolicy.letters);
}
- if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
+ if (mPasswordPolicy.numeric != PasswordPolicy.DEF_MINIMUM_NUMERIC) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_NUMERIC, minimumPasswordMetrics.numeric);
+ out, TAG_MIN_PASSWORD_NUMERIC, mPasswordPolicy.numeric);
}
- if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
+ if (mPasswordPolicy.symbols != PasswordPolicy.DEF_MINIMUM_SYMBOLS) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_SYMBOLS, minimumPasswordMetrics.symbols);
+ out, TAG_MIN_PASSWORD_SYMBOLS, mPasswordPolicy.symbols);
}
- if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
+ if (mPasswordPolicy.nonLetter > PasswordPolicy.DEF_MINIMUM_NON_LETTER) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_NONLETTER, minimumPasswordMetrics.nonLetter);
+ out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
}
}
if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
@@ -1383,31 +1395,31 @@
info.readPoliciesFromXml(parser);
}
} else if (TAG_PASSWORD_QUALITY.equals(tag)) {
- minimumPasswordMetrics.quality = Integer.parseInt(
+ mPasswordPolicy.quality = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
- minimumPasswordMetrics.length = Integer.parseInt(
+ mPasswordPolicy.length = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
passwordHistoryLength = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
- minimumPasswordMetrics.upperCase = Integer.parseInt(
+ mPasswordPolicy.upperCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
- minimumPasswordMetrics.lowerCase = Integer.parseInt(
+ mPasswordPolicy.lowerCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
- minimumPasswordMetrics.letters = Integer.parseInt(
+ mPasswordPolicy.letters = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
- minimumPasswordMetrics.numeric = Integer.parseInt(
+ mPasswordPolicy.numeric = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
- minimumPasswordMetrics.symbols = Integer.parseInt(
+ mPasswordPolicy.symbols = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
- minimumPasswordMetrics.nonLetter = Integer.parseInt(
+ mPasswordPolicy.nonLetter = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
}else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
@@ -1668,23 +1680,23 @@
pw.decreaseIndent();
}
pw.print("passwordQuality=0x");
- pw.println(Integer.toHexString(minimumPasswordMetrics.quality));
+ pw.println(Integer.toHexString(mPasswordPolicy.quality));
pw.print("minimumPasswordLength=");
- pw.println(minimumPasswordMetrics.length);
+ pw.println(mPasswordPolicy.length);
pw.print("passwordHistoryLength=");
pw.println(passwordHistoryLength);
pw.print("minimumPasswordUpperCase=");
- pw.println(minimumPasswordMetrics.upperCase);
+ pw.println(mPasswordPolicy.upperCase);
pw.print("minimumPasswordLowerCase=");
- pw.println(minimumPasswordMetrics.lowerCase);
+ pw.println(mPasswordPolicy.lowerCase);
pw.print("minimumPasswordLetters=");
- pw.println(minimumPasswordMetrics.letters);
+ pw.println(mPasswordPolicy.letters);
pw.print("minimumPasswordNumeric=");
- pw.println(minimumPasswordMetrics.numeric);
+ pw.println(mPasswordPolicy.numeric);
pw.print("minimumPasswordSymbols=");
- pw.println(minimumPasswordMetrics.symbols);
+ pw.println(mPasswordPolicy.symbols);
pw.print("minimumPasswordNonLetter=");
- pw.println(minimumPasswordMetrics.nonLetter);
+ pw.println(mPasswordPolicy.nonLetter);
pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
pw.print("strongAuthUnlockTimeout=");
@@ -1984,6 +1996,11 @@
return LocalServices.getService(LockSettingsInternal.class);
}
+ IPlatformCompat getIPlatformCompat() {
+ return IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ }
+
boolean hasUserSetupCompleted(DevicePolicyData userData) {
return userData.mUserSetupComplete;
}
@@ -2222,6 +2239,7 @@
mUsageStatsManagerInternal = Preconditions.checkNotNull(
injector.getUsageStatsManagerInternal());
mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
+ mIPlatformCompat = Preconditions.checkNotNull(injector.getIPlatformCompat());
mIPermissionManager = Preconditions.checkNotNull(injector.getIPermissionManager());
mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager());
@@ -4135,15 +4153,15 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
final long ident = mInjector.binderClearCallingIdentity();
try {
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.quality != quality) {
- metrics.quality = quality;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.quality != quality) {
+ passwordPolicy.quality = quality;
resetInactivePasswordRequirementsIfRPlus(userId, ap);
updatePasswordValidityCheckpointLocked(userId, parent);
updatePasswordQualityCacheForUserGroup(userId);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
@@ -4156,23 +4174,33 @@
.write();
}
+ private boolean passwordQualityInvocationOrderCheckEnabled(String packageName, int userId) {
+ try {
+ return mIPlatformCompat.isChangeEnabledByPackageName(ADMIN_APP_PASSWORD_COMPLEXITY,
+ packageName);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+ }
+ return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q;
+ }
+
/**
* For admins targeting R+ reset various password constraints to default values when quality is
* set to a value that makes those constraints that have no effect.
*/
private void resetInactivePasswordRequirementsIfRPlus(int userId, ActiveAdmin admin) {
- if (getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
- final PasswordMetrics metrics = admin.minimumPasswordMetrics;
- if (metrics.quality < PASSWORD_QUALITY_NUMERIC) {
- metrics.length = ActiveAdmin.DEF_MINIMUM_PASSWORD_LENGTH;
+ if (passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(), userId)) {
+ final PasswordPolicy policy = admin.mPasswordPolicy;
+ if (policy.quality < PASSWORD_QUALITY_NUMERIC) {
+ policy.length = PasswordPolicy.DEF_MINIMUM_LENGTH;
}
- if (metrics.quality < PASSWORD_QUALITY_COMPLEX) {
- metrics.letters = ActiveAdmin.DEF_MINIMUM_PASSWORD_LETTERS;
- metrics.upperCase = ActiveAdmin.DEF_MINIMUM_PASSWORD_UPPER_CASE;
- metrics.lowerCase = ActiveAdmin.DEF_MINIMUM_PASSWORD_LOWER_CASE;
- metrics.numeric = ActiveAdmin.DEF_MINIMUM_PASSWORD_NUMERIC;
- metrics.symbols = ActiveAdmin.DEF_MINIMUM_PASSWORD_SYMBOLS;
- metrics.nonLetter = ActiveAdmin.DEF_MINIMUM_PASSWORD_NON_LETTER;
+ if (policy.quality < PASSWORD_QUALITY_COMPLEX) {
+ policy.letters = PasswordPolicy.DEF_MINIMUM_LETTERS;
+ policy.upperCase = PasswordPolicy.DEF_MINIMUM_UPPER_CASE;
+ policy.lowerCase = PasswordPolicy.DEF_MINIMUM_LOWER_CASE;
+ policy.numeric = PasswordPolicy.DEF_MINIMUM_NUMERIC;
+ policy.symbols = PasswordPolicy.DEF_MINIMUM_SYMBOLS;
+ policy.nonLetter = PasswordPolicy.DEF_MINIMUM_NON_LETTER;
}
}
}
@@ -4237,7 +4265,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordMetrics.quality : mode;
+ return admin != null ? admin.mPasswordPolicy.quality : mode;
}
// Return the strictest policy across all participating admins.
@@ -4246,8 +4274,8 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (mode < admin.minimumPasswordMetrics.quality) {
- mode = admin.minimumPasswordMetrics.quality;
+ if (mode < admin.mPasswordPolicy.quality) {
+ mode = admin.mPasswordPolicy.quality;
}
}
return mode;
@@ -4307,14 +4335,14 @@
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_NUMERIC, "setPasswordMinimumLength");
- if (metrics.length != length) {
- metrics.length = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.length != length) {
+ passwordPolicy.length = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LENGTH)
@@ -4325,8 +4353,9 @@
private void ensureMinimumQuality(
int userId, ActiveAdmin admin, int minimumQuality, String operation) {
- if (admin.minimumPasswordMetrics.quality < minimumQuality
- && getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
+ if (admin.mPasswordPolicy.quality < minimumQuality
+ && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
+ userId)) {
throw new IllegalStateException(String.format(
"password quality should be at least %d for %s", minimumQuality, operation));
}
@@ -4335,7 +4364,7 @@
@Override
public int getPasswordMinimumLength(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.length, PASSWORD_QUALITY_NUMERIC);
+ admin -> admin.mPasswordPolicy.length, PASSWORD_QUALITY_NUMERIC);
}
@Override
@@ -4564,13 +4593,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumUpperCase");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.upperCase != length) {
- metrics.upperCase = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.upperCase != length) {
+ passwordPolicy.upperCase = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_UPPER_CASE)
@@ -4582,7 +4611,7 @@
@Override
public int getPasswordMinimumUpperCase(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.upperCase, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.upperCase, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4594,13 +4623,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumLowerCase");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.lowerCase != length) {
- metrics.lowerCase = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.lowerCase != length) {
+ passwordPolicy.lowerCase = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LOWER_CASE)
@@ -4612,7 +4641,7 @@
@Override
public int getPasswordMinimumLowerCase(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.lowerCase, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.lowerCase, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4626,13 +4655,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumLetters");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.letters != length) {
- metrics.letters = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.letters != length) {
+ passwordPolicy.letters = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LETTERS)
@@ -4644,7 +4673,7 @@
@Override
public int getPasswordMinimumLetters(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.letters, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.letters, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4658,13 +4687,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumNumeric");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.numeric != length) {
- metrics.numeric = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.numeric != length) {
+ passwordPolicy.numeric = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NUMERIC)
@@ -4676,7 +4705,7 @@
@Override
public int getPasswordMinimumNumeric(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.numeric, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.numeric, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4690,13 +4719,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumSymbols");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.symbols != length) {
- ap.minimumPasswordMetrics.symbols = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.symbols != length) {
+ ap.mPasswordPolicy.symbols = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_SYMBOLS)
@@ -4708,7 +4737,7 @@
@Override
public int getPasswordMinimumSymbols(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.symbols, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.symbols, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4723,13 +4752,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumNonLetter");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.nonLetter != length) {
- ap.minimumPasswordMetrics.nonLetter = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.nonLetter != length) {
+ ap.mPasswordPolicy.nonLetter = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NON_LETTER)
@@ -4741,7 +4770,7 @@
@Override
public int getPasswordMinimumNonLetter(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.nonLetter, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.nonLetter, PASSWORD_QUALITY_COMPLEX);
}
/**
@@ -4777,6 +4806,33 @@
}
}
+ /**
+ * Calculates strictest (maximum) value for a given password property enforced by admin[s].
+ */
+ @Override
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ return getPasswordMinimumMetrics(userHandle, false /* parent */);
+ }
+
+ /**
+ * Calculates strictest (maximum) value for a given password property enforced by admin[s].
+ */
+ private PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle, boolean parent) {
+ if (!mHasFeature) {
+ new PasswordMetrics(LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ }
+ enforceFullCrossUsersPermission(userHandle);
+ ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>();
+ synchronized (getLockObject()) {
+ List<ActiveAdmin> admins =
+ getActiveAdminsForLockscreenPoliciesLocked(userHandle, parent);
+ for (ActiveAdmin admin : admins) {
+ adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
+ }
+ }
+ return PasswordMetrics.merge(adminMetrics);
+ }
+
@Override
public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
if (!mHasFeature) {
@@ -4792,8 +4848,9 @@
int credentialOwner = getCredentialOwner(userHandle, parent);
DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
- return isActivePasswordSufficientForUserLocked(
+ boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked(
policy.mPasswordValidAtLastCheckpoint, metrics, userHandle, parent);
+ return activePasswordSufficientForUserLocked;
}
}
@@ -4856,25 +4913,11 @@
*/
private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
@NonNull PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
- final int requiredQuality = getPasswordQuality(null, userId, parent);
-
- if (requiredQuality >= PASSWORD_QUALITY_NUMERIC
- && metrics.length < getPasswordMinimumLength(null, userId, parent)) {
- return false;
- }
-
- // PASSWORD_QUALITY_COMPLEX doesn't represent actual password quality, it means that number
- // of characters of each class should be checked instead of quality itself.
- if (requiredQuality == PASSWORD_QUALITY_COMPLEX) {
- return metrics.upperCase >= getPasswordMinimumUpperCase(null, userId, parent)
- && metrics.lowerCase >= getPasswordMinimumLowerCase(null, userId, parent)
- && metrics.letters >= getPasswordMinimumLetters(null, userId, parent)
- && metrics.numeric >= getPasswordMinimumNumeric(null, userId, parent)
- && metrics.symbols >= getPasswordMinimumSymbols(null, userId, parent)
- && metrics.nonLetter >= getPasswordMinimumNonLetter(null, userId, parent);
- } else {
- return metrics.quality >= requiredQuality;
- }
+ PasswordMetrics minMetrics = getPasswordMinimumMetrics(userId, parent);
+ final List<PasswordValidationError> passwordValidationErrors =
+ PasswordMetrics.validatePasswordMetrics(
+ minMetrics, PASSWORD_COMPLEXITY_NONE, false, metrics);
+ return passwordValidationErrors.isEmpty();
}
@Override
@@ -5132,77 +5175,17 @@
private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
int flags, int callingUid, int userHandle) {
- int quality;
synchronized (getLockObject()) {
- quality = getPasswordQuality(null, userHandle, /* parent */ false);
- if (quality == PASSWORD_QUALITY_MANAGED) {
- quality = PASSWORD_QUALITY_UNSPECIFIED;
- }
// TODO(b/120484642): remove getBytes() below
- final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password.getBytes());
- final int realQuality = metrics.quality;
- if (realQuality < quality && quality != PASSWORD_QUALITY_COMPLEX) {
- Slog.w(LOG_TAG, "resetPassword: password quality 0x"
- + Integer.toHexString(realQuality)
- + " does not meet required quality 0x"
- + Integer.toHexString(quality));
+ final PasswordMetrics minMetrics = getPasswordMinimumMetrics(userHandle);
+ final List<PasswordValidationError> validationErrors =
+ PasswordMetrics.validatePassword(
+ minMetrics, PASSWORD_COMPLEXITY_NONE, false, password.getBytes());
+ if (!validationErrors.isEmpty()) {
+ Log.w(LOG_TAG, "Failed to reset password due to constraint violation: "
+ + validationErrors.get(0));
return false;
}
- quality = Math.max(realQuality, quality);
- int length = getPasswordMinimumLength(null, userHandle, /* parent */ false);
- if (password.length() < length) {
- Slog.w(LOG_TAG, "resetPassword: password length " + password.length()
- + " does not meet required length " + length);
- return false;
- }
- if (quality == PASSWORD_QUALITY_COMPLEX) {
- int neededLetters = getPasswordMinimumLetters(null, userHandle, /* parent */ false);
- if(metrics.letters < neededLetters) {
- Slog.w(LOG_TAG, "resetPassword: number of letters " + metrics.letters
- + " does not meet required number of letters " + neededLetters);
- return false;
- }
- int neededNumeric = getPasswordMinimumNumeric(null, userHandle, /* parent */ false);
- if (metrics.numeric < neededNumeric) {
- Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + metrics.numeric
- + " does not meet required number of numerical digits "
- + neededNumeric);
- return false;
- }
- int neededLowerCase = getPasswordMinimumLowerCase(
- null, userHandle, /* parent */ false);
- if (metrics.lowerCase < neededLowerCase) {
- Slog.w(LOG_TAG, "resetPassword: number of lowercase letters "
- + metrics.lowerCase
- + " does not meet required number of lowercase letters "
- + neededLowerCase);
- return false;
- }
- int neededUpperCase = getPasswordMinimumUpperCase(
- null, userHandle, /* parent */ false);
- if (metrics.upperCase < neededUpperCase) {
- Slog.w(LOG_TAG, "resetPassword: number of uppercase letters "
- + metrics.upperCase
- + " does not meet required number of uppercase letters "
- + neededUpperCase);
- return false;
- }
- int neededSymbols = getPasswordMinimumSymbols(null, userHandle, /* parent */ false);
- if (metrics.symbols < neededSymbols) {
- Slog.w(LOG_TAG, "resetPassword: number of special symbols " + metrics.symbols
- + " does not meet required number of special symbols " + neededSymbols);
- return false;
- }
- int neededNonLetter = getPasswordMinimumNonLetter(
- null, userHandle, /* parent */ false);
- if (metrics.nonLetter < neededNonLetter) {
- Slog.w(LOG_TAG, "resetPassword: number of non-letter characters "
- + metrics.nonLetter
- + " does not meet required number of non-letter characters "
- + neededNonLetter);
- return false;
- }
- }
}
DevicePolicyData policy = getUserData(userHandle);
@@ -5222,28 +5205,20 @@
// back in to the service.
final long ident = mInjector.binderClearCallingIdentity();
final boolean result;
+ final LockscreenCredential newCredential =
+ LockscreenCredential.createPasswordOrNone(password);
try {
if (token == null) {
// This is the legacy reset password for DPM. Here we want to be able to override
// the old device password without necessarily knowing it.
- if (!TextUtils.isEmpty(password)) {
- mLockPatternUtils.saveLockPassword(password.getBytes(), null, quality,
- userHandle, /*allowUntrustedChange */true);
- } else {
- mLockPatternUtils.clearLock(null, userHandle,
- /*allowUntrustedChange */ true);
- }
+ mLockPatternUtils.setLockCredential(
+ newCredential,
+ LockscreenCredential.createNone(),
+ userHandle, /*allowUntrustedChange */true);
result = true;
} else {
- if (!TextUtils.isEmpty(password)) {
- result = mLockPatternUtils.setLockCredentialWithToken(password.getBytes(),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- quality, tokenHandle, token, userHandle);
- } else {
- result = mLockPatternUtils.setLockCredentialWithToken(null,
- LockPatternUtils.CREDENTIAL_TYPE_NONE,
- quality, tokenHandle, token, userHandle);
- }
+ result = mLockPatternUtils.setLockCredentialWithToken(newCredential, tokenHandle,
+ token, userHandle);
}
boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
if (requireEntry) {
@@ -11574,10 +11549,10 @@
/**
* Returns true if specified admin is allowed to limit passwords and has a
- * {@code minimumPasswordMetrics.quality} of at least {@code minPasswordQuality}
+ * {@code mPasswordPolicy.quality} of at least {@code minPasswordQuality}
*/
private static boolean isLimitPasswordAllowed(ActiveAdmin admin, int minPasswordQuality) {
- if (admin.minimumPasswordMetrics.quality < minPasswordQuality) {
+ if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
@@ -14196,13 +14171,13 @@
}
private void maybeLogPasswordComplexitySet(ComponentName who, int userId, boolean parent,
- PasswordMetrics metrics) {
+ PasswordPolicy passwordPolicy) {
if (SecurityLog.isLoggingEnabled()) {
final int affectedUserId = parent ? getProfileParentId(userId) : userId;
SecurityLog.writeEvent(SecurityLog.TAG_PASSWORD_COMPLEXITY_SET, who.getPackageName(),
- userId, affectedUserId, metrics.length, metrics.quality, metrics.letters,
- metrics.nonLetter, metrics.numeric, metrics.upperCase, metrics.lowerCase,
- metrics.symbols);
+ userId, affectedUserId, passwordPolicy.length, passwordPolicy.quality,
+ passwordPolicy.letters, passwordPolicy.nonLetter, passwordPolicy.numeric,
+ passwordPolicy.upperCase, passwordPolicy.lowerCase, passwordPolicy.symbols);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
index 0838fbc..7cfbcc8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
@@ -51,7 +51,7 @@
static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
static final String CTL_STOP = "ctl.stop";
- static final String REMOTE_BUGREPORT_SERVICE = "bugreportremote";
+ static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4cf98d3..88859a7 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -921,7 +921,6 @@
false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
false);
- boolean disableSlices = SystemProperties.getBoolean("config.disable_slices", false);
boolean enableLeftyService = SystemProperties.getBoolean("config.enable_lefty", false);
boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
@@ -1856,7 +1855,7 @@
t.traceEnd();
}
- if (!disableSlices) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
t.traceBegin("StartSliceManagerService");
mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS);
t.traceEnd();
diff --git a/services/print/java/com/android/server/print/TEST_MAPPING b/services/print/java/com/android/server/print/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/services/print/java/com/android/server/print/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPrintTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index edf82ee..597d337 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -41,12 +41,14 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -65,9 +67,14 @@
"com.android.server.accessibility", "AccessibilityServiceConnectionTest");
static final int SERVICE_ID = 42;
+ // Mock package-private AccessibilityUserState class
+ @Rule
+ public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+ new DexmakerShareClassLoaderRule();
+
AccessibilityServiceConnection mConnection;
- @Mock AccessibilityManagerService.UserState mMockUserState;
+ @Mock AccessibilityUserState mMockUserState;
@Mock Context mMockContext;
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
new file mode 100644
index 0000000..9180054
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.testing.DexmakerShareClassLoaderRule;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for AccessibilityUserState */
+public class AccessibilityUserStateTest {
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("com.android.server.accessibility", "AccessibilityUserStateTest");
+
+ // Values of setting key SHOW_IME_WITH_HARD_KEYBOARD
+ private static final int STATE_HIDE_IME = 0;
+ private static final int STATE_SHOW_IME = 1;
+
+ private static final int USER_ID = 42;
+
+ // Mock package-private class AccessibilityServiceConnection
+ @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+ new DexmakerShareClassLoaderRule();
+
+ @Mock private AccessibilityServiceInfo mMockServiceInfo;
+
+ @Mock private AccessibilityServiceConnection mMockConnection;
+
+ @Mock private AccessibilityUserState.ServiceInfoChangeListener mMockListener;
+
+ @Mock private Context mContext;
+
+ private MockContentResolver mMockResolver;
+
+ private AccessibilityUserState mUserState;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ FakeSettingsProvider.clearSettingsProvider();
+ mMockResolver = new MockContentResolver();
+ mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mMockResolver);
+ when(mMockServiceInfo.getComponentName()).thenReturn(COMPONENT_NAME);
+ when(mMockConnection.getServiceInfo()).thenReturn(mMockServiceInfo);
+
+ mUserState = new AccessibilityUserState(USER_ID, mContext, mMockListener);
+ }
+
+ @After
+ public void tearDown() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @Test
+ public void onSwitchToAnotherUser_userStateClearedNonDefaultValues() {
+ mUserState.getBoundServicesLocked().add(mMockConnection);
+ mUserState.getBindingServicesLocked().add(COMPONENT_NAME);
+ mUserState.setLastSentClientStateLocked(
+ STATE_FLAG_ACCESSIBILITY_ENABLED
+ | STATE_FLAG_TOUCH_EXPLORATION_ENABLED
+ | STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED);
+ mUserState.setNonInteractiveUiTimeoutLocked(30);
+ mUserState.setInteractiveUiTimeoutLocked(30);
+ mUserState.mEnabledServices.add(COMPONENT_NAME);
+ mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
+ mUserState.setTouchExplorationEnabledLocked(true);
+ mUserState.setDisplayMagnificationEnabledLocked(true);
+ mUserState.setNavBarMagnificationEnabledLocked(true);
+ mUserState.setServiceAssignedToAccessibilityButtonLocked(COMPONENT_NAME);
+ mUserState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
+ mUserState.setAutoclickEnabledLocked(true);
+ mUserState.setUserNonInteractiveUiTimeoutLocked(30);
+ mUserState.setUserInteractiveUiTimeoutLocked(30);
+
+ mUserState.onSwitchToAnotherUserLocked();
+
+ verify(mMockConnection).unbindLocked();
+ assertTrue(mUserState.getBoundServicesLocked().isEmpty());
+ assertTrue(mUserState.getBindingServicesLocked().isEmpty());
+ assertEquals(-1, mUserState.getLastSentClientStateLocked());
+ assertEquals(0, mUserState.getNonInteractiveUiTimeoutLocked());
+ assertEquals(0, mUserState.getInteractiveUiTimeoutLocked());
+ assertTrue(mUserState.mEnabledServices.isEmpty());
+ assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
+ assertFalse(mUserState.isTouchExplorationEnabledLocked());
+ assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
+ assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
+ assertNull(mUserState.getServiceAssignedToAccessibilityButtonLocked());
+ assertFalse(mUserState.isNavBarMagnificationAssignedToAccessibilityButtonLocked());
+ assertFalse(mUserState.isAutoclickEnabledLocked());
+ assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
+ assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
+ }
+
+ @Test
+ public void addService_connectionAlreadyAdded_notAddAgain() {
+ mUserState.getBoundServicesLocked().add(mMockConnection);
+
+ mUserState.addServiceLocked(mMockConnection);
+
+ verify(mMockConnection, never()).onAdded();
+ }
+
+ @Test
+ public void addService_connectionNotYetAddedToBoundService_addAndNotifyServices() {
+ when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+
+ mUserState.addServiceLocked(mMockConnection);
+
+ verify(mMockConnection).onAdded();
+ assertTrue(mUserState.getBoundServicesLocked().contains(mMockConnection));
+ assertEquals(mMockConnection, mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME));
+ verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState));
+ }
+
+ @Test
+ public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() {
+ // When soft kb show mode is hidden in settings and is auto in state.
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_HIDDEN, USER_ID);
+
+ mUserState.reconcileSoftKeyboardModeWithSettingsLocked();
+
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ assertEquals(SHOW_MODE_AUTO, getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ assertNull(mUserState.getServiceChangingSoftKeyboardModeLocked());
+ }
+
+ @Test
+ public void
+ reconcileSoftKeyboardMode_stateIgnoreHardKb_settingsShowImeHardKb_setAutoOverride() {
+ // When show mode is ignore hard kb without original hard kb value
+ // and show ime with hard kb is hide
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_IGNORE_HARD_KEYBOARD, USER_ID);
+ mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, COMPONENT_NAME);
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ STATE_HIDE_IME, USER_ID);
+
+ mUserState.reconcileSoftKeyboardModeWithSettingsLocked();
+
+ assertEquals(SHOW_MODE_AUTO | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
+ getSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ assertNull(mUserState.getServiceChangingSoftKeyboardModeLocked());
+ }
+
+ @Test
+ public void removeService_serviceChangingSoftKeyboardMode_removeAndSetSoftKbModeAuto() {
+ mUserState.setServiceChangingSoftKeyboardModeLocked(COMPONENT_NAME);
+ mUserState.mComponentNameToServiceMap.put(COMPONENT_NAME, mMockConnection);
+ mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME);
+
+ mUserState.removeServiceLocked(mMockConnection);
+
+ assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
+ verify(mMockConnection).onRemoved();
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ assertNull(mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME));
+ verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState));
+ }
+
+ @Test
+ public void serviceDisconnected_removeServiceAndAddToBinding() {
+ when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+ mUserState.addServiceLocked(mMockConnection);
+
+ mUserState.serviceDisconnectedLocked(mMockConnection);
+
+ assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
+ assertTrue(mUserState.getBindingServicesLocked().contains(COMPONENT_NAME));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_withInvalidShowMode_shouldKeepDefaultAuto() {
+ final int invalidShowMode = SHOW_MODE_HIDDEN | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+
+ assertFalse(mUserState.setSoftKeyboardModeLocked(invalidShowMode, null));
+
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ }
+
+ @Test
+ public void setSoftKeyboardMode_newModeSameWithCurrentState_returnTrue() {
+ when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+ mUserState.addServiceLocked(mMockConnection);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_withIgnoreHardKb_whenHardKbOverridden_returnFalseAdNoChange() {
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_AUTO | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN, USER_ID);
+
+ assertFalse(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, null));
+
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ }
+
+ @Test
+ public void
+ setSoftKeyboardMode_withIgnoreHardKb_whenShowImeWithHardKb_setOriginalHardKbValue() {
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, STATE_SHOW_IME, USER_ID);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, null));
+
+ assertEquals(SHOW_MODE_IGNORE_HARD_KEYBOARD | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE,
+ getSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_whenCurrentIgnoreHardKb_shouldSetShowImeWithHardKbValue() {
+ mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, COMPONENT_NAME);
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, STATE_HIDE_IME, USER_ID);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_IGNORE_HARD_KEYBOARD | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE, USER_ID);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null));
+
+ assertEquals(STATE_SHOW_IME, getSecureIntForUser(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, USER_ID));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_withRequester_shouldUpdateInternalStateAndSettingsAsIs() {
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME));
+
+ assertEquals(SHOW_MODE_HIDDEN, mUserState.getSoftKeyboardShowModeLocked());
+ assertEquals(SHOW_MODE_HIDDEN, getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ assertEquals(COMPONENT_NAME, mUserState.getServiceChangingSoftKeyboardModeLocked());
+ }
+
+ @Test
+ public void setSoftKeyboardMode_shouldNotifyBoundService() {
+ mUserState.addServiceLocked(mMockConnection);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME));
+
+ verify(mMockConnection).notifySoftKeyboardShowModeChangedLocked(eq(SHOW_MODE_HIDDEN));
+ }
+
+ private int getSecureIntForUser(String key, int userId) {
+ return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
+ }
+
+ private void putSecureIntForUser(String key, int value, int userId) {
+ Settings.Secure.putIntForUser(mMockResolver, key, value, userId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index deb6f71..8da927d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -56,7 +56,6 @@
MessageCapturingHandler mMessageCapturingHandler;
- @Mock AccessibilityManagerService.UserState mMockUserState;
@Mock Context mMockContext;
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 9a1fd9c..aeccfc5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,10 +25,12 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
+import static android.app.admin.PasswordMetrics.computeForPassword;
import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.server.testutils.TestUtils.assertExpectException;
@@ -93,7 +95,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
@@ -4266,9 +4268,9 @@
assertTrue(dpm.isResetPasswordTokenActive(admin1));
// test reset password with token
- when(getServices().lockPatternUtils.setLockCredentialWithToken(eq(password.getBytes()),
- eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD),
- eq(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), eq(handle), eq(token),
+ when(getServices().lockPatternUtils.setLockCredentialWithToken(
+ eq(LockscreenCredential.createPassword(password)),
+ eq(handle), eq(token),
eq(UserHandle.USER_SYSTEM)))
.thenReturn(true);
assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
@@ -4295,11 +4297,7 @@
reset(mContext.spiedContext);
- PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
- 8, 2,
- 6, 1,
- 0, 1);
+ PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes());
setActivePasswordState(passwordMetricsNoSymbols);
assertTrue(dpm.isActivePasswordSufficient());
@@ -4326,11 +4324,7 @@
reset(mContext.spiedContext);
assertFalse(dpm.isActivePasswordSufficient());
- PasswordMetrics passwordMetricsWithSymbols = new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
- 7, 2,
- 5, 1,
- 1, 2);
+ PasswordMetrics passwordMetricsWithSymbols = computeForPassword("abcd.XY5".getBytes());
setActivePasswordState(passwordMetricsWithSymbols);
assertTrue(dpm.isActivePasswordSufficient());
@@ -4347,7 +4341,7 @@
final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
// When there is no lockscreen, user password metrics is always empty.
when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle))
- .thenReturn(new PasswordMetrics());
+ .thenReturn(new PasswordMetrics(CREDENTIAL_TYPE_NONE));
// If no password requirements are set, isActivePasswordSufficient should succeed.
assertTrue(dpm.isActivePasswordSufficient());
@@ -5314,7 +5308,7 @@
.thenReturn(DpmMockContext.CALLER_USER_HANDLE);
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
- .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPassword("asdf".getBytes()));
assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
}
@@ -5331,10 +5325,10 @@
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
- .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPassword("asdf".getBytes()));
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(parentUser.id))
- .thenReturn(PasswordMetrics.computeForPassword("parentUser".getBytes()));
+ .thenReturn(computeForPassword("parentUser".getBytes()));
assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 1fcd0ef..baf1ed0 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -127,4 +127,57 @@
assertEquals(rule1, matchedRule);
}
+
+ @Test
+ public void testMatchRules_validForm() {
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_1),
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+ AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE)));
+ Rule rule = new Rule(
+ openFormula, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+ APP_INSTALL_METADATA);
+
+ assertEquals(rule, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_ruleNotInDNF() {
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.OR, Arrays.asList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_1),
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+ AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE)));
+ Rule rule = new Rule(
+ openFormula, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+ APP_INSTALL_METADATA);
+
+ assertEquals(Rule.EMPTY, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_openFormulaWithNot() {
+ OpenFormula openSubFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_2),
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+ AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE)));
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT,
+ Collections.singletonList(openSubFormula));
+ Rule rule = new Rule(
+ openFormula, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+ APP_INSTALL_METADATA);
+
+ assertEquals(Rule.EMPTY, matchedRule);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
index d1fa0f9..048ee70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
@@ -19,6 +19,7 @@
import static com.android.server.testutils.TestUtils.assertExpectException;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
@@ -83,4 +84,20 @@
assertEquals(String.format("Rule: PACKAGE_NAME EQ %s AND APP_CERTIFICATE EQ %s, DENY",
PACKAGE_NAME, APP_CERTIFICATE), toString);
}
+
+ @Test
+ public void testEquals_trueCase() {
+ Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+ Rule rule2 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+
+ assertEquals(rule1, rule2);
+ }
+
+ @Test
+ public void testEquals_falseCase() {
+ Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+ Rule rule2 = new Rule(APP_CERTIFICATE_ATOMIC_FORMULA, DENY_EFFECT);
+
+ assertNotEquals(rule1, rule2);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index f9ac022..537287d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -35,7 +35,9 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.face.Face;
import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.os.FileUtils;
import android.os.IProgressListener;
@@ -249,11 +251,32 @@
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(userId)).thenReturn(true);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Fingerprint fp = (Fingerprint) invocation.getArguments()[0];
+ FingerprintManager.RemovalCallback callback =
+ (FingerprintManager.RemovalCallback) invocation.getArguments()[2];
+ callback.onRemovalSucceeded(fp, 0);
+ return null;
+ }
+ }).when(mFingerprintManager).remove(any(), eq(userId), any());
+
// Hardware must be detected and templates must be enrolled
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(userId)).thenReturn(true);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Face face = (Face) invocation.getArguments()[0];
+ FaceManager.RemovalCallback callback =
+ (FaceManager.RemovalCallback) invocation.getArguments()[2];
+ callback.onRemovalSucceeded(face, 0);
+ return null;
+ }
+ }).when(mFaceManager).remove(any(), eq(userId), any());
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index c00d33b..b60111e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -19,10 +19,9 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
@@ -48,6 +47,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import org.junit.Before;
import org.junit.Test;
@@ -55,6 +56,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
/**
* Test class for {@link LockSettingsShellCommand}.
*
@@ -87,24 +90,30 @@
public void testWrongPassword() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(false);
assertEquals(-1, mCommand.exec(mBinder, in, out, err,
new String[] { "set-pin", "--old", "1234" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils, never()).saveLockPassword(any(byte[].class), any(byte[].class),
- anyInt(), anyInt());
+ verify(mLockPatternUtils, never()).setLockCredential(any(), any(),
+ anyInt(), anyBoolean());
}
@Test
public void testChangePin() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+ PASSWORD_QUALITY_NUMERIC);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-pin", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
- PASSWORD_QUALITY_NUMERIC, mUserId);
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createPin("4321"),
+ LockscreenCredential.createPin("1234"),
+ mUserId);
}
@Test
@@ -121,12 +130,17 @@
public void testChangePassword() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+ PASSWORD_QUALITY_ALPHABETIC);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-password", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
- PASSWORD_QUALITY_ALPHABETIC, mUserId);
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createPassword("4321"),
+ LockscreenCredential.createPassword("1234"),
+ mUserId);
}
@Test
@@ -143,11 +157,15 @@
public void testChangePattern() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-pattern", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234".getBytes(),
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createPattern(stringToPattern("4321")),
+ LockscreenCredential.createPattern(stringToPattern("1234")),
mUserId);
}
@@ -165,10 +183,19 @@
public void testClear() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "clear", "--old", "1234" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).clearLock("1234".getBytes(), mUserId);
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createNone(),
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId);
+ }
+
+ private List<LockPatternView.Cell> stringToPattern(String str) {
+ return LockPatternUtils.byteArrayToPattern(str.getBytes());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 2a169b7..cb51897 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -434,35 +434,6 @@
assertEquals(2, PersistentData.TYPE_SP_WEAVER);
}
- public void testCredentialHash_serializeUnserialize() {
- byte[] serialized = CredentialHash.create(
- PAYLOAD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD).toBytes();
- CredentialHash deserialized = CredentialHash.fromBytes(serialized);
-
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
- assertArrayEquals(PAYLOAD, deserialized.hash);
- }
-
- public void testCredentialHash_unserialize_versionGatekeeper() {
- // This test ensures that we can read serialized VERSION_GATEKEEPER CredentialHashes
- // even if we change the wire format in the future.
- byte[] serialized = new byte[] {
- 1, /* VERSION_GATEKEEPER */
- 2, /* CREDENTIAL_TYPE_PASSWORD */
- 0, 0, 0, 5, /* hash length */
- 1, 2, -1, -2, 33, /* hash */
- };
- CredentialHash deserialized = CredentialHash.fromBytes(serialized);
-
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
- assertArrayEquals(PAYLOAD, deserialized.hash);
-
- // Make sure the constants we use on the wire do not change.
- assertEquals(-1, LockPatternUtils.CREDENTIAL_TYPE_NONE);
- assertEquals(1, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
- assertEquals(2, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
- }
-
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 0776589..42ca42a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
@@ -364,7 +365,7 @@
// Verify DPM gets notified about new device lock
flushHandlerTasks();
final PasswordMetrics metric = PasswordMetrics.computeForCredential(
- LockPatternUtils.CREDENTIAL_TYPE_PATTERN, pattern);
+ LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(pattern)));
assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
@@ -512,7 +513,7 @@
assertFalse(mService.havePattern(PRIMARY_USER_ID));
}
- public void testgetHashFactorPrimaryUser() throws RemoteException {
+ public void testGetHashFactorPrimaryUser() throws RemoteException {
final byte[] password = "password".getBytes();
mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
@@ -527,7 +528,7 @@
assertArrayEquals(hashFactor, newHashFactor);
}
- public void testgetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
+ public void testGetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
final byte[] pattern = "1236".getBytes();
mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
null, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
@@ -535,7 +536,7 @@
assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
}
- public void testgetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
+ public void testGetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
final byte[] primaryPassword = "primary".getBytes();
final byte[] profilePassword = "profile".getBytes();
mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index ba12b73..8a48904 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -113,6 +113,7 @@
import android.net.NetworkTemplate;
import android.net.StringNetworkSpecifier;
import android.os.Binder;
+import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.PersistableBundle;
import android.os.PowerManagerInternal;
@@ -1117,7 +1118,7 @@
// Define simple data plan
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), DataUnit.MEGABYTES.toBytes(1800));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
mServiceContext.getOpPackageName());
// We're 20% through the month (6 days)
@@ -1241,7 +1242,7 @@
// Define simple data plan which gives us effectively 60MB/day
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), DataUnit.MEGABYTES.toBytes(1800));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
mServiceContext.getOpPackageName());
// We're 20% through the month (6 days)
@@ -1457,6 +1458,8 @@
when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+ when(mTelephonyManager.createForSubscriptionId(FAKE_SUB_ID))
+ .thenReturn(mock(TelephonyManager.class));
PersistableBundle bundle = CarrierConfigManager.getDefaultConfig();
when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle);
setNetworkPolicies(buildDefaultFakeMobilePolicy());
@@ -1468,6 +1471,8 @@
when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+ when(mTelephonyManager.createForSubscriptionId(FAKE_SUB_ID))
+ .thenReturn(mock(TelephonyManager.class));
when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null);
setNetworkPolicies(buildDefaultFakeMobilePolicy());
// smoke test to make sure no errors are raised
@@ -1653,7 +1658,7 @@
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"),
DataUnit.MEGABYTES.toBytes(1800));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1674,7 +1679,7 @@
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"),
DataUnit.MEGABYTES.toBytes(100));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1690,7 +1695,7 @@
{
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), BYTES_UNLIMITED);
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1707,7 +1712,7 @@
{
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), BYTES_UNLIMITED);
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1923,6 +1928,8 @@
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(
new int[] { TEST_SUB_ID });
when(mTelephonyManager.getSubscriberId(TEST_SUB_ID)).thenReturn(TEST_IMSI);
+ when(mTelephonyManager.createForSubscriptionId(TEST_SUB_ID))
+ .thenReturn(mock(TelephonyManager.class));
doNothing().when(mTelephonyManager).setPolicyDataEnabled(anyBoolean(), anyInt());
expectNetworkState(false /* roaming */);
}
@@ -2049,6 +2056,23 @@
private FutureIntent mRestrictBackgroundChanged;
+ private void postMsgAndWaitForCompletion() throws InterruptedException {
+ final Handler handler = mService.getHandlerForTesting();
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.getHandlerForTesting().post(latch::countDown);
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for the test msg to be handled");
+ }
+ }
+
+ private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
+ throws InterruptedException {
+ mService.setSubscriptionPlans(subId, plans, callingPackage);
+ // setSubscriptionPlans() triggers async events, wait for those to be completed before
+ // moving forward as they could interfere with the tests later.
+ postMsgAndWaitForCompletion();
+ }
+
private void setRestrictBackground(boolean flag) throws Exception {
mService.setRestrictBackground(flag);
// Sanity check.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 43bcd4f..7c2a097 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -167,6 +167,7 @@
/* userId */ 456,
/* installerPackageName */ "testInstaller",
/* installerUid */ -1,
+ InstallSource.create("testInstaller"),
/* sessionParams */ params,
/* createdMillis */ 0L,
/* stageDir */ mTmpDir,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 8c3373f..3ae5674 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -105,6 +106,7 @@
private int mUid = 1000;
private int mPid = 2000;
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+ private NotificationChannel mChannel;
private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
@@ -158,6 +160,8 @@
mService.mScreenOn = false;
mService.mInCallStateOffHook = false;
mService.mNotificationPulseEnabled = true;
+
+ mChannel = new NotificationChannel("test", "test", IMPORTANCE_HIGH);
}
//
@@ -174,13 +178,18 @@
true /* noisy */, false /* buzzy*/, false /* lights */);
}
+ private NotificationRecord getBeepyOtherNotification() {
+ return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */);
+ }
+
private NotificationRecord getBeepyOnceNotification() {
return getNotificationRecord(mId, false /* insistent */, true /* once */,
true /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getQuietNotification() {
- return getNotificationRecord(mId, false /* insistent */, false /* once */,
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
false /* noisy */, false /* buzzy*/, false /* lights */);
}
@@ -214,6 +223,11 @@
false /* noisy */, true /* buzzy*/, false /* lights */);
}
+ private NotificationRecord getBuzzyOtherNotification() {
+ return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+ false /* noisy */, true /* buzzy*/, false /* lights */);
+ }
+
private NotificationRecord getBuzzyOnceNotification() {
return getNotificationRecord(mId, false /* insistent */, true /* once */,
false /* noisy */, true /* buzzy*/, false /* lights */);
@@ -239,22 +253,34 @@
false /* noisy */, false /* buzzy*/, true /* lights */);
}
- private NotificationRecord getCallRecord(int id, boolean insistent) {
- return getNotificationRecord(id, false, false /* once */, true /* noisy */,
- false /* buzzy */, false /* lights */, false /* default vib */,
- false /* default sound */, false /* default lights */, "",
- Notification.GROUP_ALERT_ALL, false);
+ private NotificationRecord getCallRecord(int id, NotificationChannel channel, boolean looping) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH);
+ Notification n = builder.build();
+ if (looping) {
+ n.flags |= Notification.FLAG_INSISTENT;
+ }
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
+ mPid, n, mUser, null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+ mService.addNotification(r);
+
+ return r;
}
private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
boolean noisy, boolean buzzy, boolean lights) {
- return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
- null, Notification.GROUP_ALERT_ALL, false);
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
+ lights, null, Notification.GROUP_ALERT_ALL, false);
}
- private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, boolean once,
+ private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent,
+ boolean once,
boolean noisy, boolean buzzy, boolean lights) {
- return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true,
+ true,
null, Notification.GROUP_ALERT_ALL, true);
}
@@ -265,16 +291,16 @@
private NotificationRecord getLightsNotificationRecord(String groupKey,
int groupAlertBehavior) {
- return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true, true,
- true, groupKey, groupAlertBehavior, false);
+ return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true,
+ true, true, groupKey, groupAlertBehavior, false);
}
- private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
+ private NotificationRecord getNotificationRecord(int id,
+ boolean insistent, boolean once,
boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
boolean isLeanback) {
- NotificationChannel channel =
- new NotificationChannel("test", "test", IMPORTANCE_HIGH);
+
final Builder builder = new Builder(getContext())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -285,31 +311,37 @@
if (noisy) {
if (defaultSound) {
defaults |= Notification.DEFAULT_SOUND;
- channel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+ mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
} else {
builder.setSound(CUSTOM_SOUND);
- channel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
+ mChannel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
}
} else {
- channel.setSound(null, null);
+ mChannel.setSound(null, null);
}
if (buzzy) {
if (defaultVibration) {
defaults |= Notification.DEFAULT_VIBRATE;
} else {
builder.setVibrate(CUSTOM_VIBRATION);
- channel.setVibrationPattern(CUSTOM_VIBRATION);
+ mChannel.setVibrationPattern(CUSTOM_VIBRATION);
}
- channel.enableVibration(true);
+ mChannel.enableVibration(true);
+ } else {
+ mChannel.setVibrationPattern(null);
+ mChannel.enableVibration(false);
}
+
if (lights) {
if (defaultLights) {
defaults |= Notification.DEFAULT_LIGHTS;
} else {
builder.setLights(CUSTOM_LIGHT_COLOR, CUSTOM_LIGHT_ON, CUSTOM_LIGHT_OFF);
}
- channel.enableLights(true);
+ mChannel.enableLights(true);
+ } else {
+ mChannel.enableLights(false);
}
builder.setDefaults(defaults);
@@ -329,7 +361,7 @@
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(context, sbn, channel);
+ NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
mService.addNotification(r);
return r;
}
@@ -339,18 +371,19 @@
//
private void verifyNeverBeep() throws RemoteException {
- verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
- anyBoolean(), (AudioAttributes) anyObject());
+ verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any());
}
- private void verifyBeep() throws RemoteException {
- verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
- eq(true), (AudioAttributes) anyObject());
+ private void verifyBeepUnlooped() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any());
}
- private void verifyBeepLooped() throws RemoteException {
- verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
- eq(false), (AudioAttributes) anyObject());
+ private void verifyBeepLooped() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any());
+ }
+
+ private void verifyBeep(int times) throws RemoteException {
+ verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any());
}
private void verifyNeverStopAudio() throws RemoteException {
@@ -362,24 +395,31 @@
}
private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
- anyString(), (AudioAttributes) anyObject());
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), any());
}
private void verifyVibrate() {
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
- anyString(), (AudioAttributes) anyObject());
+ anyString(), any());
+ }
+
+ private void verifyVibrate(int times) {
+ verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(), any());
}
private void verifyVibrateLooped() {
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
- anyString(), (AudioAttributes) anyObject());
+ anyString(), any());
}
private void verifyDelayedVibrateLooped() {
verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
- argThat(mVibrateLoopMatcher), anyString(),
- (AudioAttributes) anyObject());
+ argThat(mVibrateLoopMatcher), anyString(), any());
+ }
+
+ private void verifyDelayedVibrate() {
+ verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
+ argThat(mVibrateOnceMatcher), anyString(), any());
}
private void verifyStopVibrate() {
@@ -398,11 +438,6 @@
verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
}
- private void verifyCustomLights() {
- verify(mLight, times(1)).setFlashing(
- eq(CUSTOM_LIGHT_COLOR), anyInt(), eq(CUSTOM_LIGHT_ON), eq(CUSTOM_LIGHT_OFF));
- }
-
//
// Tests
//
@@ -425,7 +460,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
verifyNeverVibrate();
verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
assertTrue(r.isInterruptive());
@@ -438,7 +473,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeep();
+ verifyBeepLooped();
assertTrue(r.isInterruptive());
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@@ -492,7 +527,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(r.isInterruptive());
}
@@ -533,7 +568,7 @@
// update should beep
r.isUpdate = true;
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertTrue(r.isInterruptive());
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
@@ -723,7 +758,7 @@
mService.buzzBeepBlinkLocked(r);
verifyNeverVibrate();
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(r.isInterruptive());
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@@ -821,7 +856,7 @@
mService.buzzBeepBlinkLocked(summary);
- verifyBeepLooped();
+ verifyBeepUnlooped();
// summaries are never interruptive for notification counts
assertFalse(summary.isInterruptive());
assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
@@ -833,7 +868,7 @@
mService.buzzBeepBlinkLocked(nonGroup);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(nonGroup.isInterruptive());
assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@@ -856,7 +891,7 @@
mService.buzzBeepBlinkLocked(child);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(child.isInterruptive());
assertNotEquals(-1, child.getLastAudiblyAlertedMs());
}
@@ -867,7 +902,7 @@
mService.buzzBeepBlinkLocked(nonGroup);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(nonGroup.isInterruptive());
assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@@ -878,7 +913,7 @@
mService.buzzBeepBlinkLocked(group);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(group.isInterruptive());
assertNotEquals(-1, group.getLastAudiblyAlertedMs());
}
@@ -1293,7 +1328,11 @@
@Test
public void testListenerHintCall() throws Exception {
- NotificationRecord r = getCallRecord(1, true);
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
@@ -1310,7 +1349,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
}
@Test
@@ -1326,7 +1365,11 @@
@Test
public void testListenerHintBoth() throws Exception {
- NotificationRecord r = getCallRecord(1, true);
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
NotificationRecord s = getBeepyNotification();
mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS
@@ -1340,7 +1383,11 @@
@Test
public void testListenerHintNotification_callSound() throws Exception {
- NotificationRecord r = getCallRecord(1, true);
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
@@ -1349,6 +1396,167 @@
verifyBeepLooped();
}
+ @Test
+ public void testCannotInterruptRingtoneInsistentBeep() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ mService.addNotification(ringtoneNotification);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ assertTrue(mService.shouldMuteNotificationLocked(interrupter));
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(1);
+
+ assertFalse(interrupter.isInterruptive());
+ assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testCannotInterruptRingtoneInsistentBuzz() {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Uri.EMPTY,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyVibrateLooped();
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ assertTrue(mService.shouldMuteNotificationLocked(interrupter));
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(1);
+
+ assertFalse(interrupter.isInterruptive());
+ assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testCanInterruptRingtoneNonInsistentBeep() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepUnlooped();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testCanInterruptRingtoneNonInsistentBuzz() {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(null,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyVibrate();
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+
+ mService.clearSoundLocked();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testRingtoneInsistentBuzz_doesNotBlockFutureSoundsOnceStopped() {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(null,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyVibrateLooped();
+
+ mService.clearVibrateLocked();
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testCanInterruptNonRingtoneInsistentBeep() throws Exception {
+ NotificationChannel fakeRingtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testCanInterruptNonRingtoneInsistentBuzz() throws Exception {
+ NotificationChannel fakeRingtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ fakeRingtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 4ea2fc0..cd0f4f1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -537,6 +537,18 @@
return new NotificationRecord(mContext, sbn, channel);
}
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
+ if (channel == null) {
+ channel = mTestNotificationChannel;
+ }
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+ nb.build(), new UserHandle(userId), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
private Map<String, Answer> getSignalExtractorSideEffects() {
Map<String, Answer> answers = new ArrayMap<>();
@@ -5451,6 +5463,112 @@
}
@Test
+ public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // A notification exists for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsBefore.length);
+
+ reset(mPackageManager);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri, nr.sbn.getUid());
+
+ // Grant permission called for the UID of SystemUI under the target user ID
+ verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+ eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+ eq(nr.sbn.getUserId()));
+ }
+
+ @Test
+ public void testGrantInlineReplyUriPermission_userAll() throws Exception {
+ // generate a NotificationRecord for USER_ALL to make sure it's converted into USER_SYSTEM
+ NotificationRecord nr =
+ generateNotificationRecord(mTestNotificationChannel, UserHandle.USER_ALL);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // A notification exists for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsBefore.length);
+
+ reset(mPackageManager);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri, nr.sbn.getUid());
+
+ // Target user for the grant is USER_ALL instead of USER_SYSTEM
+ verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+ eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+ eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testGrantInlineReplyUriPermission_acrossUsers() throws Exception {
+ // generate a NotificationRecord for USER_ALL to make sure it's converted into USER_SYSTEM
+ int otherUserId = 11;
+ NotificationRecord nr =
+ generateNotificationRecord(mTestNotificationChannel, otherUserId);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // A notification exists for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsBefore.length);
+
+ reset(mPackageManager);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+ int uid = 0; // sysui on primary user
+ int otherUserUid = (otherUserId * 100000) + 1; // SystemUI as a different user
+ String sysuiPackage = "sysui";
+ final String[] sysuiPackages = new String[] { sysuiPackage };
+ when(mPackageManager.getPackagesForUid(uid)).thenReturn(sysuiPackages);
+
+ // Make sure to mock call for USER_SYSTEM and not USER_ALL, since it's been replaced by the
+ // time this is called
+ when(mPackageManager.getPackageUid(sysuiPackage, 0, otherUserId))
+ .thenReturn(otherUserUid);
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+
+ // Target user for the grant is USER_ALL instead of USER_SYSTEM
+ verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+ eq(otherUserUid), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+ eq(otherUserId));
+ }
+
+ @Test
+ public void testGrantInlineReplyUriPermission_noRecordExists() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ waitForIdle();
+
+ // No notifications exist for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifsBefore.length);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+ int uid = 0; // sysui on primary user
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+
+ // Grant permission not called if no record exists for the given key
+ verify(mUgm, times(0)).grantUriPermissionFromOwner(any(), anyInt(), any(),
+ eq(uri), anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index aaaa7a5..2836e69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -28,6 +28,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
@@ -128,7 +129,7 @@
mActivityMetricsLogger.notifyActivityLaunching(intent);
- verifyAsync(mLaunchObserver).onIntentStarted(eq(intent));
+ verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -163,12 +164,12 @@
testOnActivityLaunched();
mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecord.getWindowingMode(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord));
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord), anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -186,6 +187,16 @@
}
@Test
+ public void testOnReportFullyDrawn() throws Exception {
+ testOnActivityLaunched();
+
+ mActivityMetricsLogger.logAppTransitionReportedDrawn(mActivityRecord, false);
+
+ verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mActivityRecord), anyLong());
+ verifyNoMoreInteractions(mLaunchObserver);
+ }
+
+ @Test
public void testOnActivityLaunchedTrampoline() throws Exception {
testOnIntentStarted();
@@ -206,12 +217,13 @@
testOnActivityLaunchedTrampoline();
mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecordTrampoline.getWindowingMode(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline));
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline),
+ anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index c2a05c24..2835c1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -891,7 +891,7 @@
activity.app = null;
overlayActivity.app = null;
- assertEquals(2, mTask.mActivities.size());
+ assertEquals(2, mTask.getChildCount());
mStack.finishDisabledPackageActivitiesLocked(activity.packageName,
null /* filterByClasses */, true /* doit */, true /* evenPersistent */,
@@ -900,7 +900,7 @@
// Although the overlay activity is in another package, the non-overlay activities are
// removed from the task. Since the overlay activity should be removed as well, the task
// should be empty.
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
@@ -918,11 +918,11 @@
// second activity will be immediately removed as it has no state.
secondActivity.setSavedState(null /* savedState */);
- assertEquals(2, mTask.mActivities.size());
+ assertEquals(2, mTask.getChildCount());
mStack.handleAppDiedLocked(secondActivity.app);
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
@@ -936,7 +936,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertEquals(1, mTask.mActivities.size());
+ assertEquals(1, mTask.getChildCount());
assertEquals(1, mStack.getAllTasks().size());
}
@@ -950,7 +950,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
@@ -964,7 +964,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertEquals(1, mTask.mActivities.size());
+ assertEquals(1, mTask.getChildCount());
assertEquals(1, mStack.getAllTasks().size());
}
@@ -978,7 +978,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 77fbdcf..d43fe63 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -215,23 +215,6 @@
return this;
}
- static Pair<Intent, ActivityInfo> createIntentAndActivityInfo() {
- // TODO: Look into consolidating with dup. code in build() method below.
- final int id = sCurrentActivityId++;
- final ComponentName component = ComponentName.createRelative(
- DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_CLASS_NAME + id);
-
- final Intent intent = new Intent();
- intent.setComponent(component);
-
- final ActivityInfo aInfo = new ActivityInfo();
- aInfo.applicationInfo = new ApplicationInfo();
- aInfo.applicationInfo.packageName = component.getPackageName();
- aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- aInfo.packageName = component.getPackageName();
- return new Pair<>(intent, aInfo);
- }
-
ActivityRecord build() {
if (mComponent == null) {
final int id = sCurrentActivityId++;
@@ -249,6 +232,7 @@
intent.setComponent(mComponent);
final ActivityInfo aInfo = new ActivityInfo();
aInfo.applicationInfo = new ApplicationInfo();
+ aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
aInfo.applicationInfo.packageName = mComponent.getPackageName();
aInfo.applicationInfo.uid = mUid;
aInfo.packageName = mComponent.getPackageName();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 650a911..b174251 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -57,7 +57,7 @@
private TaskStack mStack;
private Task mTask;
- private AppWindowToken mToken;
+ private ActivityRecord mToken;
public void setUpOnDisplay(DisplayContent dc) {
mStack = createTaskStackOnDisplay(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, dc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 605d520..14939cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -101,10 +101,10 @@
@Test
@FlakyTest(bugId = 131005232)
public void testTransitWithinTask() {
- final AppWindowToken opening = createAppWindowToken(mDisplayContent,
+ final ActivityRecord opening = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
opening.setOccludesParent(false);
- final AppWindowToken closing = createAppWindowToken(mDisplayContent,
+ final ActivityRecord closing = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
closing.setOccludesParent(false);
final Task task = opening.getTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 72d9bd0..9d53676 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -159,7 +159,7 @@
final TaskStack stack1 = createTaskStackOnDisplay(dc1);
final Task task1 = createTaskInStack(stack1, 0 /* userId */);
- final AppWindowToken token1 =
+ final ActivityRecord token1 =
WindowTestUtils.createTestAppWindowToken(dc1);
task1.addChild(token1, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 2661735..b4c978f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -77,7 +77,7 @@
TaskStack mStack;
Task mTask;
- AppWindowToken mToken;
+ ActivityRecord mToken;
private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
@@ -410,7 +410,7 @@
}
private AppWindowToken createTestAppWindowTokenForGivenTask(Task task) {
- final AppWindowToken appToken =
+ final ActivityRecord appToken =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task.addChild(appToken, 0);
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2ba3cbd..f12c349 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -241,7 +241,7 @@
assertEquals(dc, stack.getDisplayContent());
final Task task = createTaskInStack(stack, 0 /* userId */);
- final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(dc);
+ final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(dc);
task.addChild(token, 0);
assertEquals(dc, task.getDisplayContent());
assertEquals(dc, token.getDisplayContent());
@@ -313,7 +313,7 @@
// Add stack with activity.
final TaskStack stack0 = createTaskStackOnDisplay(dc0);
final Task task0 = createTaskInStack(stack0, 0 /* userId */);
- final AppWindowToken token =
+ final ActivityRecord token =
WindowTestUtils.createTestAppWindowToken(dc0);
task0.addChild(token, 0);
dc0.configureDisplayPolicy();
@@ -321,7 +321,7 @@
final TaskStack stack1 = createTaskStackOnDisplay(dc1);
final Task task1 = createTaskInStack(stack1, 0 /* userId */);
- final AppWindowToken token1 =
+ final ActivityRecord token1 =
WindowTestUtils.createTestAppWindowToken(dc0);
task1.addChild(token1, 0);
dc1.configureDisplayPolicy();
@@ -682,16 +682,15 @@
// is appWin & null on the other display.
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
newDisplay.setInputMethodWindowLocked(null);
- assertTrue("appWin should be IME target window",
- appWin.equals(mDisplayContent.mInputMethodTarget));
+ assertEquals("appWin should be IME target window",
+ appWin, mDisplayContent.mInputMethodTarget);
assertNull("newDisplay Ime target: ", newDisplay.mInputMethodTarget);
// Switch input method window on new display & make sure the input method target also
// switched as expected.
newDisplay.setInputMethodWindowLocked(mImeWindow);
mDisplayContent.setInputMethodWindowLocked(null);
- assertTrue("appWin1 should be IME target window",
- appWin1.equals(newDisplay.mInputMethodTarget));
+ assertEquals("appWin1 should be IME target window", appWin1, newDisplay.mInputMethodTarget);
assertNull("default display Ime target: ", mDisplayContent.mInputMethodTarget);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 304df22..452e06f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -96,7 +96,7 @@
* Creates a window state which can be used as a drop target.
*/
private WindowState createDropTargetWindow(String name, int ownerId) {
- final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(
+ final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(
mDisplayContent);
final TaskStack stack = createTaskStackOnDisplay(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
index 92ddb35..eef680b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -54,7 +54,7 @@
// Stack should contain visible app window to be considered visible.
final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
assertFalse(mPinnedStack.isVisible());
- final AppWindowToken pinnedApp =
+ final ActivityRecord pinnedApp =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
pinnedTask.addChild(pinnedApp, 0 /* addPos */);
assertTrue(mPinnedStack.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 2eb6ea4..d045073 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -68,13 +68,13 @@
public void testClosingAppDifferentStackOrientation() {
final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task1 = createTaskInStack(stack, 0 /* userId */);
- AppWindowToken appWindowToken1 =
+ ActivityRecord appWindowToken1 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task1.addChild(appWindowToken1, 0);
appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
final Task task2 = createTaskInStack(stack, 1 /* userId */);
- AppWindowToken appWindowToken2 =
+ ActivityRecord appWindowToken2 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
@@ -88,13 +88,13 @@
public void testMoveTaskToBackDifferentStackOrientation() {
final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task1 = createTaskInStack(stack, 0 /* userId */);
- AppWindowToken appWindowToken1 =
+ ActivityRecord appWindowToken1 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task1.addChild(appWindowToken1, 0);
appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
final Task task2 = createTaskInStack(stack, 1 /* userId */);
- AppWindowToken appWindowToken2 =
+ ActivityRecord appWindowToken2 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 09e5027..6a94137 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -115,4 +115,8 @@
@Override
public void showInsets(int types, boolean fromIme) throws RemoteException {
}
+
+ @Override
+ public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index c627c19..f44c969 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -19,17 +19,11 @@
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.ActivityTestsBase.ActivityBuilder.createIntentAndActivityInfo;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.app.ActivityManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.os.IBinder;
-import android.util.Pair;
import android.view.IWindow;
import android.view.WindowManager;
@@ -51,23 +45,22 @@
}
/** Creates an {@link AppWindowToken} and adds it to the specified {@link Task}. */
- static AppWindowToken createAppWindowTokenInTask(DisplayContent dc, Task task) {
- final AppWindowToken newToken = createTestAppWindowToken(dc);
+ static ActivityRecord createAppWindowTokenInTask(DisplayContent dc, Task task) {
+ final ActivityRecord newToken = createTestAppWindowToken(dc);
task.addChild(newToken, POSITION_TOP);
return newToken;
}
- static AppWindowToken createTestAppWindowToken(DisplayContent dc) {
+ static ActivityRecord createTestAppWindowToken(DisplayContent dc) {
synchronized (dc.mWmService.mGlobalLock) {
- Pair<Intent, ActivityInfo> pair = createIntentAndActivityInfo();
- final AppWindowToken token = new AppWindowToken(dc.mWmService,
- dc.mWmService.mAtmService, new ActivityRecord.Token(pair.first), pair.second,
- null, pair.first, dc);
- token.setOccludesParent(true);
- token.setHidden(false);
- token.hiddenRequested = false;
- spyOn(token);
- return token;
+ final ActivityRecord r =
+ new ActivityTestsBase.ActivityBuilder(dc.mWmService.mAtmService)
+ .build();
+ r.onDisplayChanged(dc);
+ r.setOccludesParent(true);
+ r.setHidden(false);
+ r.hiddenRequested = false;
+ return r;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 4c4b21e..1fce46c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -204,15 +204,15 @@
}
}
- AppWindowToken createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) {
+ ActivityRecord createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) {
return createTestAppWindowToken(dc, windowingMode, activityType);
}
- AppWindowToken createTestAppWindowToken(DisplayContent dc, int
+ ActivityRecord createTestAppWindowToken(DisplayContent dc, int
windowingMode, int activityType) {
final TaskStack stack = createTaskStackOnDisplay(windowingMode, activityType, dc);
final Task task = createTaskInStack(stack, 0 /* userId */);
- final AppWindowToken appWindowToken =
+ final ActivityRecord appWindowToken =
WindowTestUtils.createTestAppWindowToken(dc);
task.addChild(appWindowToken, 0);
return appWindowToken;
@@ -244,7 +244,7 @@
WindowState createAppWindow(Task task, int type, String name) {
synchronized (mWm.mGlobalLock) {
- final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+ final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task.addChild(token, 0);
return createWindow(null, type, token, name);
}
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index a806320..2ff26b8 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -17,12 +17,14 @@
android_app {
name: "startop_test_app",
srcs: [
+ "src/ComplexLayoutInflationActivity.java",
"src/CPUIntensive.java",
"src/EmptyActivity.java",
- "src/LayoutInflationActivity.java",
- "src/ComplexLayoutInflationActivity.java",
"src/FrameLayoutInflationActivity.java",
+ "src/LayoutInflationActivity.java",
+ "src/NonInteractiveSystemServerBenchmarkActivity.java",
"src/SystemServerBenchmarkActivity.java",
+ "src/SystemServerBenchmarks.java",
"src/TextViewInflationActivity.java",
],
sdk_version: "26", // Android O (8.0) and higher
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index 15785d4..ebe2584 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -84,6 +84,14 @@
</intent-filter>
</activity>
+ <activity
+ android:label="Non-interactive SystemServer Benchmark"
+ android:name=".NonInteractiveSystemServerBenchmarkActivity"
+ android:exported="true" />
+
</application>
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
</manifest>
diff --git a/startop/apps/test/README.md b/startop/apps/test/README.md
index dadc66a..949dff7 100644
--- a/startop/apps/test/README.md
+++ b/startop/apps/test/README.md
@@ -24,3 +24,14 @@
inflation.
adb shell am start -n com.android.startop.test/.ComplexLayoutInflationActivity
+
+## NonInteractiveSystemServerBenchmark
+
+This activity is for running microbenchmarks from the command line. Run as follows:
+
+ adb shell am start -W -n com.android.startop.test .NonInteractiveSystemServerBenchmarkActivity
+
+It takes awhile (and there's currently no automated way to make sure it's done),
+but when it finishes, you can get the results like this:
+
+ adb shell cat /sdcard/Android/data/com.android.startop.test/files/benchmark.csv
diff --git a/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java b/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java
new file mode 100644
index 0000000..a2dc2cf
--- /dev/null
+++ b/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.startop.test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+
+public class NonInteractiveSystemServerBenchmarkActivity extends Activity {
+ ArrayList<CharSequence> benchmarkNames = new ArrayList();
+ ArrayList<Runnable> benchmarkThunks = new ArrayList();
+
+ PrintStream out;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ SystemServerBenchmarks.initializeBenchmarks(this, (name, thunk) -> {
+ benchmarkNames.add(name);
+ benchmarkThunks.add(thunk);
+ });
+
+ try {
+ out = new PrintStream(new File(getExternalFilesDir(null), "benchmark.csv"));
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ out.println("Name,Mean,Stdev");
+ runBenchmarks(0);
+ }
+
+ void runBenchmarks(int i) {
+ if (i < benchmarkNames.size()) {
+ SystemServerBenchmarks.runBenchmarkInBackground(benchmarkThunks.get(i),
+ (mean, stdev) -> {
+ out.printf("%s,%.0f,%.0f\n", benchmarkNames.get(i), mean, stdev);
+ runBenchmarks(i + 1);
+ });
+ }
+ }
+}
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
index c8d9fde..75ea69b 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -31,13 +31,25 @@
import android.widget.GridLayout;
import android.widget.TextView;
+public class SystemServerBenchmarkActivity extends Activity implements BenchmarkRunner {
+ private GridLayout benchmarkList;
-class Benchmark {
- // Time limit to run benchmarks in seconds
- public static final int TIME_LIMIT = 5;
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.system_server_benchmark_page);
- public Benchmark(ViewGroup parent, CharSequence name, Runnable thunk) {
- Context context = parent.getContext();
+ benchmarkList = findViewById(R.id.benchmark_list);
+
+ SystemServerBenchmarks.initializeBenchmarks(this, this);
+ }
+
+ /**
+ * Adds a benchmark to the set to run.
+ *
+ * @param name A short name that shows up in the UI or benchmark results
+ */
+ public void addBenchmark(CharSequence name, Runnable thunk) {
+ Context context = benchmarkList.getContext();
Button button = new Button(context);
TextView mean = new TextView(context);
TextView stdev = new TextView(context);
@@ -50,165 +62,14 @@
mean.setText("Running...");
stdev.setText("");
- new AsyncTask() {
- double resultMean = 0;
- double resultStdev = 0;
-
- @Override
- protected Object doInBackground(Object... _args) {
- long startTime = System.nanoTime();
- int count = 0;
-
- // Run benchmark
- while (true) {
- long elapsed = -System.nanoTime();
- thunk.run();
- elapsed += System.nanoTime();
-
- count++;
- double elapsedVariance = (double) elapsed - resultMean;
- resultMean += elapsedVariance / count;
- resultStdev += elapsedVariance * ((double) elapsed - resultMean);
-
- if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
- break;
- }
- }
- resultStdev = Math.sqrt(resultStdev / (count - 1));
-
- return null;
- }
-
- @Override
- protected void onPostExecute(Object _result) {
- mean.setText(String.format("%.3f", resultMean / 1e6));
- stdev.setText(String.format("%.3f", resultStdev / 1e6));
- }
- }.execute(new Object());
- });
-
- parent.addView(button);
- parent.addView(mean);
- parent.addView(stdev);
- }
-}
-
-public class SystemServerBenchmarkActivity extends Activity {
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.system_server_benchmark_page);
-
- GridLayout benchmarkList = findViewById(R.id.benchmark_list);
-
- new Benchmark(benchmarkList, "Empty", () -> {
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (1 thread)", () -> {
- CPUIntensive.doSomeWork(1);
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (2 thread)", () -> {
- CPUIntensive.doSomeWork(2);
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (4 thread)", () -> {
- CPUIntensive.doSomeWork(4);
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (8 thread)", () -> {
- CPUIntensive.doSomeWork(8);
- });
-
- PackageManager pm = getPackageManager();
- new Benchmark(benchmarkList, "getInstalledApplications", () -> {
- pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
- });
-
- new Benchmark(benchmarkList, "getInstalledPackages", () -> {
- pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
- });
-
- new Benchmark(benchmarkList, "getPackageInfo", () -> {
- try {
- pm.getPackageInfo("com.android.startop.test", 0);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
-
- new Benchmark(benchmarkList, "getApplicationInfo", () -> {
- try {
- pm.getApplicationInfo("com.android.startop.test", 0);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
-
- try {
- ApplicationInfo app = pm.getApplicationInfo("com.android.startop.test", 0);
- new Benchmark(benchmarkList, "getResourcesForApplication", () -> {
- try {
- pm.getResourcesForApplication(app);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
+ SystemServerBenchmarks.runBenchmarkInBackground(thunk, (resultMean, resultStdev) -> {
+ mean.setText(String.format("%.3f", resultMean / 1e6));
+ stdev.setText(String.format("%.3f", resultStdev / 1e6));
});
-
- new Benchmark(benchmarkList, "getPackagesForUid", () -> {
- pm.getPackagesForUid(app.uid);
- });
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
-
- ComponentName component = new ComponentName(this, this.getClass());
- new Benchmark(benchmarkList, "getActivityInfo", () -> {
- try {
- pm.getActivityInfo(component, PackageManager.GET_META_DATA);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
});
- new Benchmark(benchmarkList, "getLaunchIntentForPackage", () -> {
- pm.getLaunchIntentForPackage("com.android.startop.test");
- });
-
- new Benchmark(benchmarkList, "getPackageUid", () -> {
- try {
- pm.getPackageUid("com.android.startop.test", 0);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
-
- new Benchmark(benchmarkList, "checkPermission", () -> {
- // Check for the first permission I could find.
- pm.checkPermission("android.permission.SEND_SMS", "com.android.startop.test");
- });
-
- new Benchmark(benchmarkList, "checkSignatures", () -> {
- // Compare with settings, since settings is on both AOSP and Master builds
- pm.checkSignatures("com.android.settings", "com.android.startop.test");
- });
-
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
- new Benchmark(benchmarkList, "queryBroadcastReceivers", () -> {
- pm.queryBroadcastReceivers(intent, 0);
- });
-
- new Benchmark(benchmarkList, "hasSystemFeature", () -> {
- pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
- });
-
- new Benchmark(benchmarkList, "resolveService", () -> {
- pm.resolveService(intent, 0);
- });
-
- ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
- new Benchmark(benchmarkList, "getRunningAppProcesses", () -> {
- am.getRunningAppProcesses();
- });
-
+ benchmarkList.addView(button);
+ benchmarkList.addView(mean);
+ benchmarkList.addView(stdev);
}
}
diff --git a/startop/apps/test/src/SystemServerBenchmarks.java b/startop/apps/test/src/SystemServerBenchmarks.java
new file mode 100644
index 0000000..5918503
--- /dev/null
+++ b/startop/apps/test/src/SystemServerBenchmarks.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.startop.test;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.UserManager;
+
+/**
+ * An interface for running benchmarks and collecting results. Used so we can have both an
+ * interactive runner and a non-interactive runner.
+ */
+interface BenchmarkRunner {
+ void addBenchmark(CharSequence name, Runnable thunk);
+}
+
+interface ResultListener {
+ /**
+ * Called when a benchmark result is ready
+ *
+ * @param mean The average iteration time in nanoseconds
+ * @param stdev The standard deviation of iteration times in nanoseconds
+ */
+ void onResult(double mean, double stdev);
+}
+
+class SystemServerBenchmarks {
+ // Time limit to run benchmarks in seconds
+ public static final int TIME_LIMIT = 5;
+
+ static void initializeBenchmarks(Activity parent, BenchmarkRunner benchmarks) {
+ final String packageName = parent.getPackageName();
+
+ benchmarks.addBenchmark("Empty", () -> {
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (1 thread)", () -> {
+ CPUIntensive.doSomeWork(1);
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (2 thread)", () -> {
+ CPUIntensive.doSomeWork(2);
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (4 thread)", () -> {
+ CPUIntensive.doSomeWork(4);
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (8 thread)", () -> {
+ CPUIntensive.doSomeWork(8);
+ });
+
+ PackageManager pm = parent.getPackageManager();
+ benchmarks.addBenchmark("getInstalledApplications", () -> {
+ pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
+ });
+
+ benchmarks.addBenchmark("getInstalledPackages", () -> {
+ pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
+ });
+
+ benchmarks.addBenchmark("getPackageInfo", () -> {
+ try {
+ pm.getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("getApplicationInfo", () -> {
+ try {
+ pm.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ try {
+ ApplicationInfo app = pm.getApplicationInfo(packageName, 0);
+ benchmarks.addBenchmark("getResourcesForApplication", () -> {
+ try {
+ pm.getResourcesForApplication(app);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("getPackagesForUid", () -> {
+ pm.getPackagesForUid(app.uid);
+ });
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ ComponentName component = new ComponentName(parent, parent.getClass());
+ benchmarks.addBenchmark("getActivityInfo", () -> {
+ try {
+ pm.getActivityInfo(component, PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("getLaunchIntentForPackage", () -> {
+ pm.getLaunchIntentForPackage(packageName);
+ });
+
+ benchmarks.addBenchmark("getPackageUid", () -> {
+ try {
+ pm.getPackageUid(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("checkPermission", () -> {
+ // Check for the first permission I could find.
+ pm.checkPermission("android.permission.SEND_SMS", packageName);
+ });
+
+ benchmarks.addBenchmark("checkSignatures", () -> {
+ // Compare with settings, since settings is on both AOSP and Master builds
+ pm.checkSignatures("com.android.settings", packageName);
+ });
+
+ Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+ benchmarks.addBenchmark("queryBroadcastReceivers", () -> {
+ pm.queryBroadcastReceivers(intent, 0);
+ });
+
+ benchmarks.addBenchmark("hasSystemFeature", () -> {
+ pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+ });
+
+ benchmarks.addBenchmark("resolveService", () -> {
+ pm.resolveService(intent, 0);
+ });
+
+ ActivityManager am = (ActivityManager) parent.getSystemService(Context.ACTIVITY_SERVICE);
+ benchmarks.addBenchmark("getRunningAppProcesses", () -> {
+ am.getRunningAppProcesses();
+ });
+
+ // We use PendingIntent.getCreatorPackage, since
+ // getPackageIntentForSender is not public to us, but getCreatorPackage
+ // is just a thin wrapper around it.
+ PendingIntent pi = PendingIntent.getActivity(parent, 0, new Intent(), 0);
+ benchmarks.addBenchmark("getPackageIntentForSender", () -> {
+ pi.getCreatorPackage();
+ });
+
+ PowerManager pwr = (PowerManager) parent.getSystemService(Context.POWER_SERVICE);
+ PowerManager.WakeLock wl = pwr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "benchmark tag");
+ benchmarks.addBenchmark("WakeLock Acquire/Release", () -> {
+ wl.acquire();
+ wl.release();
+ });
+
+ AppOpsManager appOps = (AppOpsManager) parent.getSystemService(Context.APP_OPS_SERVICE);
+ int uid = Process.myUid();
+ benchmarks.addBenchmark("AppOpsService.checkOperation", () -> {
+ appOps.checkOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, uid, packageName);
+ });
+
+ benchmarks.addBenchmark("AppOpsService.checkPackage", () -> {
+ appOps.checkPackage(uid, packageName);
+ });
+
+ benchmarks.addBenchmark("AppOpsService.noteOperation", () -> {
+ appOps.noteOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, uid, packageName);
+ });
+
+ benchmarks.addBenchmark("AppOpsService.noteProxyOperation", () -> {
+ appOps.noteProxyOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, packageName);
+ });
+
+ UserManager userManager = (UserManager) parent.getSystemService(Context.USER_SERVICE);
+ benchmarks.addBenchmark("isUserUnlocked", () -> {
+ userManager.isUserUnlocked();
+ });
+
+ benchmarks.addBenchmark("getIntentSender", () -> {
+ pi.getIntentSender();
+ });
+
+ ConnectivityManager cm = (ConnectivityManager) parent
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ benchmarks.addBenchmark("getActiveNetworkInfo", () -> {
+ cm.getActiveNetworkInfo();
+ });
+ }
+
+ /**
+ * A helper method for benchark runners to actually run the benchmark and gather stats
+ *
+ * @param thunk The code whose performance we want to measure
+ * @param reporter What to do with the results
+ */
+ static void runBenchmarkInBackground(Runnable thunk, ResultListener reporter) {
+ new AsyncTask() {
+ double resultMean = 0;
+ double resultStdev = 0;
+
+ @Override
+ protected Object doInBackground(Object... _args) {
+ long startTime = System.nanoTime();
+ int count = 0;
+
+ // Run benchmark
+ while (true) {
+ long elapsed = -System.nanoTime();
+ thunk.run();
+ elapsed += System.nanoTime();
+
+ count++;
+ double elapsedVariance = (double) elapsed - resultMean;
+ resultMean += elapsedVariance / count;
+ resultStdev += elapsedVariance * ((double) elapsed - resultMean);
+
+ if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
+ break;
+ }
+ }
+ resultStdev = Math.sqrt(resultStdev / (count - 1));
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Object _result) {
+ reporter.onResult(resultMean, resultStdev);
+ }
+ }.execute(new Object());
+ }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
index acf9946..59f4d56 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -33,6 +33,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -86,10 +87,14 @@
public static final class IntentStarted extends AppLaunchEvent {
@NonNull
public final Intent intent;
+ public final long timestampNs;
- public IntentStarted(@SequenceId long sequenceId, Intent intent) {
+ public IntentStarted(@SequenceId long sequenceId,
+ Intent intent,
+ long timestampNs) {
super(sequenceId);
this.intent = intent;
+ this.timestampNs = timestampNs;
Objects.requireNonNull(intent, "intent");
}
@@ -98,14 +103,16 @@
public boolean equals(Object other) {
if (other instanceof IntentStarted) {
return intent.equals(((IntentStarted)other).intent) &&
- super.equals(other);
+ timestampNs == ((IntentStarted)other).timestampNs &&
+ super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
- return ", intent=" + intent.toString();
+ return ", intent=" + intent.toString() +
+ " , timestampNs=" + Long.toString(timestampNs);
}
@@ -113,11 +120,13 @@
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
IntentProtoParcelable.write(p, intent, flags);
+ p.writeLong(timestampNs);
}
IntentStarted(Parcel p) {
super(p);
intent = IntentProtoParcelable.create(p);
+ timestampNs = p.readLong();
}
}
@@ -154,8 +163,8 @@
@Override
public boolean equals(Object other) {
if (other instanceof BaseWithActivityRecordData) {
- return activityRecordSnapshot.equals(
- ((BaseWithActivityRecordData)other).activityRecordSnapshot) &&
+ return (Arrays.equals(activityRecordSnapshot,
+ ((BaseWithActivityRecordData)other).activityRecordSnapshot)) &&
super.equals(other);
}
return false;
@@ -163,7 +172,7 @@
@Override
protected String toStringBody() {
- return ", " + activityRecordSnapshot.toString();
+ return ", " + new String(activityRecordSnapshot);
}
@Override
@@ -200,7 +209,7 @@
@Override
protected String toStringBody() {
- return ", temperature=" + Integer.toString(temperature);
+ return super.toStringBody() + ", temperature=" + Integer.toString(temperature);
}
@Override
@@ -216,18 +225,39 @@
}
public static final class ActivityLaunchFinished extends BaseWithActivityRecordData {
+ public final long timestampNs;
+
public ActivityLaunchFinished(@SequenceId long sequenceId,
- @NonNull @ActivityRecordProto byte[] snapshot) {
+ @NonNull @ActivityRecordProto byte[] snapshot,
+ long timestampNs) {
super(sequenceId, snapshot);
+ this.timestampNs = timestampNs;
}
@Override
public boolean equals(Object other) {
- if (other instanceof ActivityLaunched) {
- return super.equals(other);
+ if (other instanceof ActivityLaunchFinished) {
+ return timestampNs == ((ActivityLaunchFinished)other).timestampNs &&
+ super.equals(other);
}
return false;
}
+
+ @Override
+ protected String toStringBody() {
+ return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ p.writeLong(timestampNs);
+ }
+
+ ActivityLaunchFinished(Parcel p) {
+ super(p);
+ timestampNs = p.readLong();
+ }
}
public static class ActivityLaunchCancelled extends AppLaunchEvent {
@@ -242,8 +272,8 @@
@Override
public boolean equals(Object other) {
if (other instanceof ActivityLaunchCancelled) {
- return Objects.equals(activityRecordSnapshot,
- ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
+ return Arrays.equals(activityRecordSnapshot,
+ ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
super.equals(other);
}
return false;
@@ -251,7 +281,7 @@
@Override
protected String toStringBody() {
- return ", " + activityRecordSnapshot.toString();
+ return super.toStringBody() + ", " + new String(activityRecordSnapshot);
}
@Override
@@ -275,6 +305,42 @@
}
}
+ public static final class ReportFullyDrawn extends BaseWithActivityRecordData {
+ public final long timestampNs;
+
+ public ReportFullyDrawn(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot,
+ long timestampNs) {
+ super(sequenceId, snapshot);
+ this.timestampNs = timestampNs;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ReportFullyDrawn) {
+ return timestampNs == ((ReportFullyDrawn)other).timestampNs &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ p.writeLong(timestampNs);
+ }
+
+ ReportFullyDrawn(Parcel p) {
+ super(p);
+ timestampNs = p.readLong();
+ }
+ }
+
@Override
public @ContentsFlags int describeContents() { return 0; }
@@ -348,6 +414,7 @@
ActivityLaunched.class,
ActivityLaunchFinished.class,
ActivityLaunchCancelled.class,
+ ReportFullyDrawn.class,
};
public static class ActivityRecordProtoParcelable {
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 902da4c..f753548 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -315,19 +315,19 @@
// All callbacks occur on the same background thread. Don't synchronize explicitly.
@Override
- public void onIntentStarted(@NonNull Intent intent) {
+ public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
// #onIntentStarted [is the only transition that] initiates a new launch sequence.
++mSequenceId;
if (DEBUG) {
- Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s)",
- mSequenceId, intent));
+ Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s, %d)",
+ mSequenceId, intent, timestampNs));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
- new AppLaunchEvent.IntentStarted(mSequenceId, intent))
+ new AppLaunchEvent.IntentStarted(mSequenceId, intent, timestampNs))
);
}
@@ -374,16 +374,34 @@
}
@Override
- public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity) {
+ public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
+ long timestampNs) {
if (DEBUG) {
- Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s)",
- mSequenceId, activity));
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s, %d)",
+ mSequenceId, activity, timestampNs));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
- new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
+ new AppLaunchEvent.ActivityLaunchFinished(mSequenceId,
+ activity,
+ timestampNs))
+ );
+ }
+
+ @Override
+ public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
+ long timestampNs) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onReportFullyDrawn(%d, %s, %d)",
+ mSequenceId, activity, timestampNs));
+ }
+
+ invokeRemote(mIorapRemote,
+ (IIorap remote) ->
+ remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ReportFullyDrawn(mSequenceId, activity, timestampNs))
);
}
}
diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt
new file mode 100644
index 0000000..51e407d
--- /dev/null
+++ b/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.google.android.startop.iorap
+
+import android.content.Intent;
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import com.google.android.startop.iorap.AppLaunchEvent;
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunched
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchCancelled
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchFinished
+import com.google.android.startop.iorap.AppLaunchEvent.IntentStarted;
+import com.google.android.startop.iorap.AppLaunchEvent.IntentFailed;
+import com.google.android.startop.iorap.AppLaunchEvent.ReportFullyDrawn
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+
+/**
+ * Basic unit tests to test all of the [AppLaunchEvent]s in [com.google.android.startop.iorap].
+ */
+@SmallTest
+class AppLaunchEventTest {
+ /**
+ * Test for IntentStarted.
+ */
+ @Test
+ fun testIntentStarted() {
+ var intent = Intent()
+ val valid = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
+ val copy = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
+ val noneCopy1 = IntentStarted(/* sequenceId= */1L, intent, /* timestampNs= */ 1L)
+ val noneCopy2 = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 2L)
+ val noneCopy3 = IntentStarted(/* sequenceId= */2L, Intent(), /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("IntentStarted{sequenceId=2, intent=Intent { } , timestampNs=1}")
+ }
+
+ /**
+ * Test for IntentFailed.
+ */
+ @Test
+ fun testIntentFailed() {
+ val valid = IntentFailed(/* sequenceId= */2L)
+ val copy = IntentFailed(/* sequenceId= */2L)
+ val noneCopy = IntentFailed(/* sequenceId= */1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("IntentFailed{sequenceId=2}")
+ }
+
+ /**
+ * Test for ActivityLaunched.
+ */
+ @Test
+ fun testActivityLaunched() {
+ //var activityRecord =
+ val valid = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val copy = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val noneCopy1 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val noneCopy2 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
+ /* temperature= */ 1)
+ val noneCopy3 = ActivityLaunched(/* sequenceId= */1L, "test1".toByteArray(),
+ /* temperature= */ 0)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunched{sequenceId=2, test, temperature=0}")
+ }
+
+
+ /**
+ * Test for ActivityLaunchFinished.
+ */
+ @Test
+ fun testActivityLaunchFinished() {
+ val valid = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val copy = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy1 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy2 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 2L)
+ val noneCopy3 = ActivityLaunchFinished(/* sequenceId= */2L, "test1".toByteArray(),
+ /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunchFinished{sequenceId=2, test, timestampNs=1}")
+ }
+
+ /**
+ * Test for ActivityLaunchCancelled.
+ */
+ @Test
+ fun testActivityLaunchCancelled() {
+ val valid = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
+ val copy = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
+ val noneCopy1 = ActivityLaunchCancelled(/* sequenceId= */1L, "test".toByteArray())
+ val noneCopy2 = ActivityLaunchCancelled(/* sequenceId= */2L, "test1".toByteArray())
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunchCancelled{sequenceId=2, test}")
+ }
+
+ /**
+ * Test for ReportFullyDrawn.
+ */
+ @Test
+ fun testReportFullyDrawn() {
+ val valid = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
+ val copy = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
+ val noneCopy1 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy2 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy3 = ReportFullyDrawn(/* sequenceId= */2L, "test1".toByteArray(),
+ /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ReportFullyDrawn{sequenceId=2, test, timestampNs=1}")
+ }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1d5c18d..9e60afc 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2817,7 +2817,7 @@
"ping_test_before_data_switch_bool";
/**
- * Controls time in milli seconds until DcTracker reevaluates 5G connection state.
+ * Controls time in milliseconds until DcTracker reevaluates 5G connection state.
* @hide
*/
public static final String KEY_5G_WATCHDOG_TIME_MS_LONG =
@@ -3199,6 +3199,13 @@
public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
"carrier_certificate_string_array";
+ /**
+ * DisconnectCause array to play busy tone. Value should be array of
+ * {@link android.telephony.DisconnectCause}.
+ */
+ public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY =
+ "disconnect_cause_play_busytone_int_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -3628,6 +3635,8 @@
sDefaults.putBoolean(KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
sDefaults.putAll(Ims.getDefaults());
sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, null);
+ sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+ new int[] {4 /* BUSY */});
}
/**
diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java
index d5e447e..46eb9df 100644
--- a/telephony/java/android/telephony/CellBroadcastService.java
+++ b/telephony/java/android/telephony/CellBroadcastService.java
@@ -21,6 +21,7 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import android.telephony.cdma.CdmaSmsCbProgramData;
/**
* A service which exposes the cell broadcast handling module to the system.
@@ -69,9 +70,11 @@
/**
* Handle a CDMA cell broadcast SMS message forwarded from the system.
* @param slotIndex the index of the slot which received the message
- * @param message the SMS PDU
+ * @param bearerData the CDMA SMS bearer data
+ * @param serviceCategory the CDMA SCPT service category
*/
- public abstract void onCdmaCellBroadcastSms(int slotIndex, byte[] message);
+ public abstract void onCdmaCellBroadcastSms(int slotIndex, byte[] bearerData,
+ @CdmaSmsCbProgramData.Category int serviceCategory);
/**
* If overriding this method, call through to the super method for any unknown actions.
@@ -102,11 +105,14 @@
/**
* Handle a CDMA cell broadcast SMS.
* @param slotIndex the index of the slot which received the broadcast
- * @param message the SMS message PDU
+ * @param bearerData the CDMA SMS bearer data
+ * @param serviceCategory the CDMA SCPT service category
*/
@Override
- public void handleCdmaCellBroadcastSms(int slotIndex, byte[] message) {
- CellBroadcastService.this.onCdmaCellBroadcastSms(slotIndex, message);
+ public void handleCdmaCellBroadcastSms(int slotIndex, byte[] bearerData,
+ int serviceCategory) {
+ CellBroadcastService.this.onCdmaCellBroadcastSms(slotIndex, bearerData,
+ serviceCategory);
}
}
}
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index cdf01da..4dc54f0 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -245,9 +245,12 @@
}
/**
- * Get the Ec/No as dB
+ * Get the Ec/No (Energy per chip over the noise spectral density) as dB.
*
- * @hide
+ * Reference: TS 25.133 Section 9.1.2.3
+ *
+ * @return the Ec/No of the measured cell in the range [-24, 1] or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable
*/
public int getEcNo() {
return mEcNo;
diff --git a/telephony/java/android/telephony/ICellBroadcastService.aidl b/telephony/java/android/telephony/ICellBroadcastService.aidl
index eff64a2..bcd6cc5 100644
--- a/telephony/java/android/telephony/ICellBroadcastService.aidl
+++ b/telephony/java/android/telephony/ICellBroadcastService.aidl
@@ -28,5 +28,5 @@
oneway void handleGsmCellBroadcastSms(int slotId, in byte[] message);
/** @see android.telephony.CellBroadcastService#onCdmaCellBroadcastSms */
- oneway void handleCdmaCellBroadcastSms(int slotId, in byte[] message);
+ oneway void handleCdmaCellBroadcastSms(int slotId, in byte[] bearerData, int serviceCategory);
}
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
index ee3c1b1..60732a3 100644
--- a/telephony/java/android/telephony/ICellInfoCallback.aidl
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -16,7 +16,6 @@
package android.telephony;
-import android.os.ParcelableException;
import android.telephony.CellInfo;
import java.util.List;
@@ -28,5 +27,5 @@
oneway interface ICellInfoCallback
{
void onCellInfo(in List<CellInfo> state);
- void onError(in int errorCode, in ParcelableException detail);
+ void onError(in int errorCode, in String exceptionName, in String message);
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index f4330fa..2d35f8e 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1814,6 +1814,36 @@
// SMS send failure result codes
+ /** @hide */
+ @IntDef(prefix = { "RESULT" }, value = {
+ RESULT_ERROR_NONE,
+ RESULT_ERROR_GENERIC_FAILURE,
+ RESULT_ERROR_RADIO_OFF,
+ RESULT_ERROR_NULL_PDU,
+ RESULT_ERROR_NO_SERVICE,
+ RESULT_ERROR_LIMIT_EXCEEDED,
+ RESULT_ERROR_FDN_CHECK_FAILURE,
+ RESULT_ERROR_SHORT_CODE_NOT_ALLOWED,
+ RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED,
+ RESULT_RADIO_NOT_AVAILABLE,
+ RESULT_NETWORK_REJECT,
+ RESULT_INVALID_ARGUMENTS,
+ RESULT_INVALID_STATE,
+ RESULT_NO_MEMORY,
+ RESULT_INVALID_SMS_FORMAT,
+ RESULT_SYSTEM_ERROR,
+ RESULT_MODEM_ERROR,
+ RESULT_NETWORK_ERROR,
+ RESULT_INVALID_SMSC_ADDRESS,
+ RESULT_OPERATION_NOT_ALLOWED,
+ RESULT_INTERNAL_ERROR,
+ RESULT_NO_RESOURCES,
+ RESULT_CANCELLED,
+ RESULT_REQUEST_NOT_SUPPORTED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Result {}
+
/**
* No error.
* @hide
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1b87657..40d057f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -53,7 +53,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.telephony.Annotation.NetworkType;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsMmTelManager;
import android.util.DisplayMetrics;
@@ -2101,13 +2100,13 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static boolean isValidSlotIndex(int slotIndex) {
- return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getMaxPhoneCount();
+ return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getSupportedModemCount();
}
/** @hide */
@UnsupportedAppUsage
public static boolean isValidPhoneId(int phoneId) {
- return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getMaxPhoneCount();
+ return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getSupportedModemCount();
}
/** @hide */
@@ -2420,8 +2419,12 @@
* @param plans the list of plans. The first plan is always the primary and
* most important plan. Any additional plans are secondary and
* may not be displayed or used by decision making logic.
+ * The list of all plans must meet the requirements defined in
+ * {@link SubscriptionPlan.Builder#setNetworkTypes(int[])}.
* @throws SecurityException if the caller doesn't meet the requirements
* outlined above.
+ * @throws IllegalArgumentException if plans don't meet the requirements
+ * mentioned above.
*/
public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
try {
@@ -2466,51 +2469,10 @@
*/
public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
@DurationMillisLong long timeoutMillis) {
- setSubscriptionOverrideUnmetered(subId, null, overrideUnmetered, timeoutMillis);
- }
-
- /**
- * Temporarily override the billing relationship between a carrier and
- * a specific subscriber to be considered unmetered for the given network
- * types. This will be reflected to apps via
- * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}.
- * This method is only accessible to the following narrow set of apps:
- * <ul>
- * <li>The carrier app for this subscriberId, as determined by
- * {@link TelephonyManager#hasCarrierPrivileges()}.
- * <li>The carrier app explicitly delegated access through
- * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
- * </ul>
- *
- * @param subId the subscriber this override applies to.
- * @param networkTypes all network types to set an override for. A null
- * network type means to apply the override to all network types.
- * Any unspecified network types will default to metered.
- * @param overrideUnmetered set if the billing relationship should be
- * considered unmetered.
- * @param timeoutMillis the timeout after which the requested override will
- * be automatically cleared, or {@code 0} to leave in the
- * requested state until explicitly cleared, or the next reboot,
- * whichever happens first.
- * @throws SecurityException if the caller doesn't meet the requirements
- * outlined above.
- * {@hide}
- */
- public void setSubscriptionOverrideUnmetered(int subId,
- @Nullable @NetworkType int[] networkTypes, boolean overrideUnmetered,
- @DurationMillisLong long timeoutMillis) {
try {
- long networkTypeMask = 0;
- if (networkTypes != null) {
- for (int networkType : networkTypes) {
- networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
- }
- } else {
- networkTypeMask = ~0;
- }
final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
- networkTypeMask, timeoutMillis, mContext.getOpPackageName());
+ timeoutMillis, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2542,52 +2504,10 @@
*/
public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
@DurationMillisLong long timeoutMillis) {
- setSubscriptionOverrideCongested(subId, null, overrideCongested, timeoutMillis);
- }
-
- /**
- * Temporarily override the billing relationship plan between a carrier and
- * a specific subscriber to be considered congested. This will cause the
- * device to delay certain network requests when possible, such as developer
- * jobs that are willing to run in a flexible time window.
- * <p>
- * This method is only accessible to the following narrow set of apps:
- * <ul>
- * <li>The carrier app for this subscriberId, as determined by
- * {@link TelephonyManager#hasCarrierPrivileges()}.
- * <li>The carrier app explicitly delegated access through
- * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
- * </ul>
- *
- * @param subId the subscriber this override applies to.
- * @param networkTypes all network types to set an override for. A null
- * network type means to apply the override to all network types.
- * Any unspecified network types will default to not congested.
- * @param overrideCongested set if the subscription should be considered
- * congested.
- * @param timeoutMillis the timeout after which the requested override will
- * be automatically cleared, or {@code 0} to leave in the
- * requested state until explicitly cleared, or the next reboot,
- * whichever happens first.
- * @throws SecurityException if the caller doesn't meet the requirements
- * outlined above.
- * @hide
- */
- public void setSubscriptionOverrideCongested(int subId,
- @Nullable @NetworkType int[] networkTypes, boolean overrideCongested,
- @DurationMillisLong long timeoutMillis) {
try {
- long networkTypeMask = 0;
- if (networkTypes != null) {
- for (int networkType : networkTypes) {
- networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
- }
- } else {
- networkTypeMask = ~0;
- }
final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
- networkTypeMask, timeoutMillis, mContext.getOpPackageName());
+ timeoutMillis, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index ec2050f..e24eb26 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -24,6 +24,7 @@
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
import android.util.Range;
import android.util.RecurrenceRule;
@@ -33,6 +34,7 @@
import java.lang.annotation.RetentionPolicy;
import java.time.Period;
import java.time.ZonedDateTime;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;
@@ -80,6 +82,8 @@
private int dataLimitBehavior = LIMIT_BEHAVIOR_UNKNOWN;
private long dataUsageBytes = BYTES_UNKNOWN;
private long dataUsageTime = TIME_UNKNOWN;
+ private @NetworkType int[] networkTypes;
+ private long networkTypesBitMask;
private SubscriptionPlan(RecurrenceRule cycleRule) {
this.cycleRule = Preconditions.checkNotNull(cycleRule);
@@ -93,6 +97,7 @@
dataLimitBehavior = source.readInt();
dataUsageBytes = source.readLong();
dataUsageTime = source.readLong();
+ networkTypes = source.createIntArray();
}
@Override
@@ -109,6 +114,7 @@
dest.writeInt(dataLimitBehavior);
dest.writeLong(dataUsageBytes);
dest.writeLong(dataUsageTime);
+ dest.writeIntArray(networkTypes);
}
@Override
@@ -121,13 +127,14 @@
.append(" dataLimitBehavior=").append(dataLimitBehavior)
.append(" dataUsageBytes=").append(dataUsageBytes)
.append(" dataUsageTime=").append(dataUsageTime)
+ .append(" networkTypes=").append(Arrays.toString(networkTypes))
.append("}").toString();
}
@Override
public int hashCode() {
return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior,
- dataUsageBytes, dataUsageTime);
+ dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes));
}
@Override
@@ -140,7 +147,8 @@
&& dataLimitBytes == other.dataLimitBytes
&& dataLimitBehavior == other.dataLimitBehavior
&& dataUsageBytes == other.dataUsageBytes
- && dataUsageTime == other.dataUsageTime;
+ && dataUsageTime == other.dataUsageTime
+ && Arrays.equals(networkTypes, other.networkTypes);
}
return false;
}
@@ -204,6 +212,32 @@
}
/**
+ * Return an array containing all {@link NetworkType}s this SubscriptionPlan applies to.
+ * A null array means this SubscriptionPlan applies to all network types.
+ */
+ public @Nullable @NetworkType int[] getNetworkTypes() {
+ return networkTypes;
+ }
+
+ /**
+ * Return the networkTypes array converted to a {@link TelephonyManager.NetworkTypeBitMask}
+ * @hide
+ */
+ public long getNetworkTypesBitMask() {
+ // calculate bitmask the first time and save for future calls
+ if (networkTypesBitMask == 0) {
+ if (networkTypes == null) {
+ networkTypesBitMask = ~0;
+ } else {
+ for (int networkType : networkTypes) {
+ networkTypesBitMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
+ }
+ }
+ }
+ return networkTypesBitMask;
+ }
+
+ /**
* Return an iterator that will return all valid data usage cycles based on
* any recurrence rules. The iterator starts from the currently active cycle
* and walks backwards through time.
@@ -335,5 +369,24 @@
plan.dataUsageTime = dataUsageTime;
return this;
}
+
+ /**
+ * Set the network types this SubscriptionPlan applies to.
+ * The developer must supply at least one plan that applies to all network types (default),
+ * and all additional plans may not include a particular network type more than once.
+ * Plan selection will prefer plans that have specific network types defined
+ * over plans that apply to all network types.
+ *
+ * @param networkTypes a set of all {@link NetworkType}s that apply to this plan.
+ * A null value or empty array means the plan applies to all network types.
+ */
+ public @NonNull Builder setNetworkTypes(@Nullable @NetworkType int[] networkTypes) {
+ if (networkTypes == null || networkTypes.length == 0) {
+ plan.networkTypes = null;
+ } else {
+ plan.networkTypes = networkTypes;
+ }
+ return this;
+ }
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2442023..03e57e7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -65,7 +65,6 @@
import android.telecom.TelecomManager;
import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.CallState;
-import android.telephony.Annotation.DataState;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
@@ -283,6 +282,21 @@
};
/** @hide */
+ @IntDef(prefix = {"MODEM_COUNT_"},
+ value = {
+ MODEM_COUNT_NO_MODEM,
+ MODEM_COUNT_SINGLE_MODEM,
+ MODEM_COUNT_DUAL_MODEM,
+ MODEM_COUNT_TRI_MODEM
+ })
+ public @interface ModemCount {}
+
+ public static final int MODEM_COUNT_NO_MODEM = 0;
+ public static final int MODEM_COUNT_SINGLE_MODEM = 1;
+ public static final int MODEM_COUNT_DUAL_MODEM = 2;
+ public static final int MODEM_COUNT_TRI_MODEM = 3;
+
+ /** @hide */
@UnsupportedAppUsage
public TelephonyManager(Context context) {
this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
@@ -359,12 +373,26 @@
/**
* Returns the number of phones available.
* Returns 0 if none of voice, sms, data is not supported
- * Returns 1 for Single standby mode (Single SIM functionality)
- * Returns 2 for Dual standby mode.(Dual SIM functionality)
- * Returns 3 for Tri standby mode.(Tri SIM functionality)
+ * Returns 1 for Single standby mode (Single SIM functionality).
+ * Returns 2 for Dual standby mode (Dual SIM functionality).
+ * Returns 3 for Tri standby mode (Tri SIM functionality).
+ * @deprecated Use {@link #getActiveModemCount} instead.
*/
+ @Deprecated
public int getPhoneCount() {
- int phoneCount = 1;
+ return getActiveModemCount();
+ }
+
+ /**
+ * Returns the number of logical modems currently configured to be activated.
+ *
+ * Returns 0 if none of voice, sms, data is not supported
+ * Returns 1 for Single standby mode (Single SIM functionality).
+ * Returns 2 for Dual standby mode (Dual SIM functionality).
+ * Returns 3 for Tri standby mode (Tri SIM functionality).
+ */
+ public @ModemCount int getActiveModemCount() {
+ int modemCount = 1;
switch (getMultiSimConfiguration()) {
case UNKNOWN:
ConnectivityManager cm = mContext == null ? null : (ConnectivityManager) mContext
@@ -372,33 +400,30 @@
// check for voice and data support, 0 if not supported
if (!isVoiceCapable() && !isSmsCapable() && cm != null
&& !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
- phoneCount = 0;
+ modemCount = MODEM_COUNT_NO_MODEM;
} else {
- phoneCount = 1;
+ modemCount = MODEM_COUNT_SINGLE_MODEM;
}
break;
case DSDS:
case DSDA:
- phoneCount = PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
+ modemCount = MODEM_COUNT_DUAL_MODEM;
break;
case TSTS:
- phoneCount = PhoneConstants.MAX_PHONE_COUNT_TRI_SIM;
+ modemCount = MODEM_COUNT_TRI_MODEM;
break;
}
- return phoneCount;
+ return modemCount;
}
/**
- *
- * Return how many phone / logical modem can be active simultaneously, in terms of device
+ * Return how many logical modem can be potentially active simultaneously, in terms of hardware
* capability.
- * For example, for a dual-SIM capable device, it always returns 2, even if only one logical
- * modem / SIM is active (aka in single SIM mode).
- *
- * TODO: b/139642279 publicize and rename.
- * @hide
+ * It might return different value from {@link #getActiveModemCount}. For example, for a
+ * dual-SIM capable device operating in single SIM mode (only one logical modem is turned on),
+ * {@link #getActiveModemCount} returns 1 while this API returns 2.
*/
- public int getMaxPhoneCount() {
+ public @ModemCount int getSupportedModemCount() {
// TODO: b/139642279 when turning on this feature, remove dependency of
// PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE and always return result based on
// PROPERTY_MAX_ACTIVE_MODEMS.
@@ -5545,18 +5570,20 @@
telephony.requestCellInfoUpdate(
getSubId(),
new ICellInfoCallback.Stub() {
+ @Override
public void onCellInfo(List<CellInfo> cellInfo) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
- public void onError(int errorCode, android.os.ParcelableException detail) {
+ @Override
+ public void onError(int errorCode, String exceptionName, String message) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onError(
- errorCode, detail.getCause())));
+ errorCode,
+ createThrowableByClassName(exceptionName, message))));
}
}, getOpPackageName());
-
} catch (RemoteException ex) {
}
}
@@ -5585,21 +5612,36 @@
telephony.requestCellInfoUpdateWithWorkSource(
getSubId(),
new ICellInfoCallback.Stub() {
+ @Override
public void onCellInfo(List<CellInfo> cellInfo) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
- public void onError(int errorCode, android.os.ParcelableException detail) {
+ @Override
+ public void onError(int errorCode, String exceptionName, String message) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onError(
- errorCode, detail.getCause())));
+ errorCode,
+ createThrowableByClassName(exceptionName, message))));
}
}, getOpPackageName(), workSource);
} catch (RemoteException ex) {
}
}
+ private static Throwable createThrowableByClassName(String className, String message) {
+ if (className == null) {
+ return null;
+ }
+ try {
+ Class<?> c = Class.forName(className);
+ return (Throwable) c.getConstructor(String.class).newInstance(message);
+ } catch (ReflectiveOperationException | ClassCastException e) {
+ }
+ return new RuntimeException(className + ": " + message);
+ }
+
/**
* Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
* PhoneStateListener.onCellInfoChanged} will be invoked.
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index 1e0d9a78..10251d7 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -885,6 +885,12 @@
*/
public static final int CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION = 1623;
+ /**
+ * The dialed RTT call should be retried without RTT
+ * @hide
+ */
+ public static final int CODE_RETRY_ON_IMS_WITHOUT_RTT = 3001;
+
/*
* OEM specific error codes. To be used by OEMs when they don't want to reveal error code which
* would be replaced by ERROR_UNSPECIFIED.
@@ -1065,6 +1071,7 @@
CODE_REJECT_VT_AVPF_NOT_ALLOWED,
CODE_REJECT_ONGOING_ENCRYPTED_CALL,
CODE_REJECT_ONGOING_CS_CALL,
+ CODE_RETRY_ON_IMS_WITHOUT_RTT,
CODE_OEM_CAUSE_1,
CODE_OEM_CAUSE_2,
CODE_OEM_CAUSE_3,
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 175769b..36ece95 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -17,6 +17,7 @@
package android.telephony.ims.stub;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.SystemApi;
import android.os.RemoteException;
import android.telephony.SmsManager;
@@ -148,14 +149,16 @@
*
* @param token unique token generated by the platform that should be used when triggering
* callbacks for this specific message.
- * @param messageRef the message reference.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param format the format of the message.
* @param smsc the Short Message Service Center address.
* @param isRetry whether it is a retry of an already attempted message or not.
* @param pdu PDU representing the contents of the message.
*/
- public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ public void sendSms(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @SmsMessage.Format String format, String smsc, boolean isRetry,
byte[] pdu) {
// Base implementation returns error. Should be overridden.
try {
@@ -172,14 +175,13 @@
* provider.
*
* @param token token provided in {@link #onSmsReceived(int, String, byte[])}
- * @param messageRef the message reference
- * @param result result of delivering the message. Valid values are:
- * {@link #DELIVER_STATUS_OK},
- * {@link #DELIVER_STATUS_ERROR_GENERIC},
- * {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
- * {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param result result of delivering the message.
*/
- public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+ public void acknowledgeSms(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @DeliverStatusResult int result) {
Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
@@ -191,12 +193,13 @@
*
* @param token token provided in {@link #onSmsStatusReportReceived(int, int, String, byte[])}
* or {@link #onSmsStatusReportReceived(int, String, byte[])}
- * @param messageRef the message reference
- * @param result result of delivering the message. Valid values are:
- * {@link #STATUS_REPORT_STATUS_OK},
- * {@link #STATUS_REPORT_STATUS_ERROR}
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param result result of delivering the message.
*/
- public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+ public void acknowledgeSmsReport(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @StatusReportResult int result) {
Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
}
@@ -210,12 +213,12 @@
* {@link #DELIVER_STATUS_ERROR_GENERIC} result code.
* @param token unique token generated by IMS providers that the platform will use to trigger
* callbacks for this message.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param format the format of the message.
* @param pdu PDU representing the contents of the message.
* @throws RuntimeException if called before {@link #onReady()} is triggered.
*/
- public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
+ public final void onSmsReceived(int token, @SmsMessage.Format String format, byte[] pdu)
+ throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -241,13 +244,16 @@
* sent successfully.
*
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
*
* @throws RuntimeException if called before {@link #onReady()} is triggered or if the
* connection to the framework is not available. If this happens attempting to send the SMS
* should be aborted.
*/
- public final void onSendSmsResultSuccess(int token, int messageRef) throws RuntimeException {
+ public final void onSendSmsResultSuccess(int token,
+ @IntRange(from = 0, to = 65535) int messageRef) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -266,34 +272,11 @@
* to the platform.
*
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
* @param status result of sending the SMS.
- * @param reason reason in case status is failure. Valid values are:
- * {@link SmsManager#RESULT_ERROR_NONE},
- * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
- * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
- * {@link SmsManager#RESULT_ERROR_NULL_PDU},
- * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
- * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
- * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
- * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
- * {@link SmsManager#RESULT_NETWORK_REJECT},
- * {@link SmsManager#RESULT_INVALID_ARGUMENTS},
- * {@link SmsManager#RESULT_INVALID_STATE},
- * {@link SmsManager#RESULT_NO_MEMORY},
- * {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
- * {@link SmsManager#RESULT_SYSTEM_ERROR},
- * {@link SmsManager#RESULT_MODEM_ERROR},
- * {@link SmsManager#RESULT_NETWORK_ERROR},
- * {@link SmsManager#RESULT_ENCODING_ERROR},
- * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
- * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
- * {@link SmsManager#RESULT_INTERNAL_ERROR},
- * {@link SmsManager#RESULT_NO_RESOURCES},
- * {@link SmsManager#RESULT_CANCELLED},
- * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
+ * @param reason reason in case status is failure.
*
* @throws RuntimeException if called before {@link #onReady()} is triggered or if the
* connection to the framework is not available. If this happens attempting to send the SMS
@@ -303,8 +286,8 @@
* send result.
*/
@Deprecated
- public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
- int reason) throws RuntimeException {
+ public final void onSendSmsResult(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @SendStatusResult int status, @SmsManager.Result int reason) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -324,34 +307,10 @@
* network.
*
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
* @param status result of sending the SMS.
- * @param reason Valid values are:
- * {@link SmsManager#RESULT_ERROR_NONE},
- * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
- * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
- * {@link SmsManager#RESULT_ERROR_NULL_PDU},
- * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
- * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
- * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
- * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
- * {@link SmsManager#RESULT_NETWORK_REJECT},
- * {@link SmsManager#RESULT_INVALID_ARGUMENTS},
- * {@link SmsManager#RESULT_INVALID_STATE},
- * {@link SmsManager#RESULT_NO_MEMORY},
- * {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
- * {@link SmsManager#RESULT_SYSTEM_ERROR},
- * {@link SmsManager#RESULT_MODEM_ERROR},
- * {@link SmsManager#RESULT_NETWORK_ERROR},
- * {@link SmsManager#RESULT_ENCODING_ERROR},
- * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
- * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
- * {@link SmsManager#RESULT_INTERNAL_ERROR},
- * {@link SmsManager#RESULT_NO_RESOURCES},
- * {@link SmsManager#RESULT_CANCELLED},
- * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
* @param networkErrorCode the error code reported by the carrier network if sending this SMS
* has resulted in an error or {@link #RESULT_NO_NETWORK_ERROR} if no network error was
* generated. See 3GPP TS 24.011 Section 7.3.4 for valid error codes and more information.
@@ -360,9 +319,9 @@
* connection to the framework is not available. If this happens attempting to send the SMS
* should be aborted.
*/
- public final void onSendSmsResultError(int token, int messageRef, @SendStatusResult int status,
- int reason, int networkErrorCode)
- throws RuntimeException {
+ public final void onSendSmsResultError(int token,
+ @IntRange(from = 0, to = 65535) int messageRef, @SendStatusResult int status,
+ @SmsManager.Result int reason, int networkErrorCode) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -384,9 +343,10 @@
* the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
* with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param format the format of the message.
* @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
*
@@ -394,7 +354,8 @@
* message reference.
*/
@Deprecated
- public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+ public final void onSmsStatusReportReceived(int token,
+ @IntRange(from = 0, to = 65535) int messageRef, @SmsMessage.Format String format,
byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
@@ -419,13 +380,12 @@
* with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @param token unique token generated by IMS providers that the platform will use to trigger
* callbacks for this message.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param format the format of the message.
* @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
*/
- public final void onSmsStatusReportReceived(int token, String format, byte[] pdu)
- throws RuntimeException {
+ public final void onSmsStatusReportReceived(int token, @SmsMessage.Format String format,
+ byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -450,13 +410,11 @@
}
/**
- * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
- * Provider.
+ * Returns the SMS format that the ImsService expects.
*
- * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @return The expected format of the SMS messages.
*/
- public String getSmsFormat() {
+ public @SmsMessage.Format String getSmsFormat() {
return SmsMessage.FORMAT_3GPP;
}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 668a6af..9e786ce 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -111,7 +111,7 @@
public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
public static final int EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED = BASE + 51;
- public static final int EVENT_5G_NETWORK_CHANGED = BASE + 52;
+ public static final int EVENT_SERVICE_STATE_CHANGED = BASE + 52;
public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index fd7ec56..39e00cc 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2063,4 +2063,9 @@
* data might be disabled on non-default data subscription but explicitly turned on by settings.
*/
boolean isDataAllowedInVoiceCall(int subId);
+
+ /**
+ * Command line command to enable or disable handling of CEP data for test purposes.
+ */
+ oneway void setCepEnabled(boolean isCepEnabled);
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 4654437..b357fa4 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -900,6 +900,20 @@
}
/**
+ * @return the bearer data byte array
+ */
+ public byte[] getEnvelopeBearerData() {
+ return mEnvelope.bearerData;
+ }
+
+ /**
+ * @return the 16-bit CDMA SCPT service category
+ */
+ public @CdmaSmsCbProgramData.Category int getEnvelopeServiceCategory() {
+ return mEnvelope.serviceCategory;
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index 2787b24..c924ab3 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -57,17 +57,17 @@
// CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT =
- CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT; // = 4096
public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT =
- CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT; // = 4097
public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT =
- CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT; // = 4098
public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY =
- CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY; // = 4099
public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE =
- CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE; // = 4100
public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE =
- CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE; // = 4351
/**
* Provides the type of a SMS message like point to point, broadcast or acknowledge
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index fcd4701..5053cee 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -758,7 +758,7 @@
/** {@hide} */
@Override
- public Context createContextAsUser(UserHandle user) {
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
throw new UnsupportedOperationException();
}
diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
index b0c7251..02dfd73 100644
--- a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
+++ b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
@@ -42,6 +42,42 @@
// https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
// https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c
+#ifndef F2FS_IOC_SET_PIN_FILE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
+#endif
+
+struct Args {
+ const char* block_device;
+ const char* file_name;
+ uint64_t byte_offset;
+ bool use_f2fs_pinning;
+};
+
+class ScopedF2fsFilePinning {
+ public:
+ explicit ScopedF2fsFilePinning(const char* file_path) {
+ fd_.reset(TEMP_FAILURE_RETRY(open(file_path, O_WRONLY | O_CLOEXEC, 0)));
+ if (fd_.get() == -1) {
+ perror("Failed to open");
+ return;
+ }
+ __u32 set = 1;
+ ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
+ }
+
+ ~ScopedF2fsFilePinning() {
+ __u32 set = 0;
+ ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
+ }
+
+ private:
+ android::base::unique_fd fd_;
+};
+
ssize_t get_logical_block_size(const char* block_device) {
android::base::unique_fd fd(open(block_device, O_RDONLY));
if (fd.get() < 0) {
@@ -138,28 +174,51 @@
return 0;
}
-int main(int argc, const char** argv) {
- if (argc != 4) {
+std::unique_ptr<Args> parse_args(int argc, const char** argv) {
+ if (argc != 4 && argc != 5) {
fprintf(stderr,
- "Usage: %s block_dev filename byte_offset\n"
+ "Usage: %s [--use-f2fs-pinning] block_dev filename byte_offset\n"
"\n"
"This program bypasses filesystem and damages the specified byte\n"
"at the physical position on <block_dev> corresponding to the\n"
"logical byte location in <filename>.\n",
argv[0]);
+ return nullptr;
+ }
+
+ auto args = std::make_unique<Args>();
+ const char** arg = &argv[1];
+ args->use_f2fs_pinning = strcmp(*arg, "--use-f2fs-pinning") == 0;
+ if (args->use_f2fs_pinning) {
+ ++arg;
+ }
+ args->block_device = *(arg++);
+ args->file_name = *(arg++);
+ args->byte_offset = strtoull(*arg, nullptr, 10);
+ if (args->byte_offset == ULLONG_MAX) {
+ perror("Invalid byte offset");
+ return nullptr;
+ }
+ return args;
+}
+
+int main(int argc, const char** argv) {
+ std::unique_ptr<Args> args = parse_args(argc, argv);
+ if (args == nullptr) {
return -1;
}
- const char* block_device = argv[1];
- const char* file_name = argv[2];
- uint64_t byte_offset = strtoull(argv[3], nullptr, 10);
-
- ssize_t block_size = get_logical_block_size(block_device);
+ ssize_t block_size = get_logical_block_size(args->block_device);
if (block_size < 0) {
return -1;
}
- int64_t physical_offset_signed = get_physical_offset(file_name, byte_offset);
+ std::unique_ptr<ScopedF2fsFilePinning> pinned_file;
+ if (args->use_f2fs_pinning) {
+ pinned_file = std::make_unique<ScopedF2fsFilePinning>(args->file_name);
+ }
+
+ int64_t physical_offset_signed = get_physical_offset(args->file_name, args->byte_offset);
if (physical_offset_signed < 0) {
return -1;
}
@@ -172,7 +231,7 @@
std::unique_ptr<char> buf(static_cast<char*>(
aligned_alloc(block_size /* alignment */, block_size /* size */)));
- if (read_block_from_device(block_device, physical_block_offset, block_size,
+ if (read_block_from_device(args->block_device, physical_block_offset, block_size,
buf.get()) < 0) {
return -1;
}
@@ -180,7 +239,7 @@
printf("before: %hhx\n", *p);
*p ^= 0xff;
printf("after: %hhx\n", *p);
- if (write_block_to_device(block_device, physical_block_offset, block_size,
+ if (write_block_to_device(args->block_device, physical_block_offset, block_size,
buf.get()) < 0) {
return -1;
}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 761c5ce..2445a6a 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -38,6 +38,7 @@
import org.junit.runner.RunWith;
import java.io.FileNotFoundException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -440,8 +441,15 @@
throws DeviceNotAvailableException {
assertTrue(path.startsWith("/data/"));
ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
- expectRemoteCommandToSucceed(String.join(" ", DAMAGING_EXECUTABLE,
- mountPoint.filesystem, path, Long.toString(offsetOfTargetingByte)));
+ ArrayList<String> args = new ArrayList<>();
+ args.add(DAMAGING_EXECUTABLE);
+ if ("f2fs".equals(mountPoint.type)) {
+ args.add("--use-f2fs-pinning");
+ }
+ args.add(mountPoint.filesystem);
+ args.add(path);
+ args.add(Long.toString(offsetOfTargetingByte));
+ expectRemoteCommandToSucceed(String.join(" ", args));
}
private String getApkPath(String packageName) throws DeviceNotAvailableException {
diff --git a/tests/Codegen/runTest.sh b/tests/Codegen/runTest.sh
index 0e90dea..929f122 100755
--- a/tests/Codegen/runTest.sh
+++ b/tests/Codegen/runTest.sh
@@ -17,11 +17,13 @@
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java && \
- cd $ANDROID_BUILD_TOP &&
- header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
- header_and_eval adb install -r -t "$(find $ANDROID_TARGET_OUT_TESTCASES -name 'CodegenTests.apk')" && \
- # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
- header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+ (
+ cd $ANDROID_BUILD_TOP &&
+ header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
+ header_and_eval adb install -r -t "$(find $ANDROID_TARGET_OUT_TESTCASES -name 'CodegenTests.apk')" && \
+ # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
+ header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+ )
exitCode=$?
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index 10eba6a..3515053 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,13 +32,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.8.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -94,8 +98,8 @@
};
@DataClass.Generated(
- time = 1570576455287L,
- codegenVersion = "1.0.7",
+ time = 1570828332402L,
+ codegenVersion = "1.0.8",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
index 1085a6a..c867409 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,13 +46,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.8.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -116,8 +120,8 @@
};
@DataClass.Generated(
- time = 1570576456245L,
- codegenVersion = "1.0.7",
+ time = 1570828333399L,
+ codegenVersion = "1.0.8",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
index 75ef963..8d097a0 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -16,6 +16,7 @@
package com.android.codegentest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
@@ -46,15 +47,22 @@
@NonNull SparseArray<SampleWithCustomBuilder> mSparseArray = null;
@NonNull SparseIntArray mSparseIntArray = null;
+ @SuppressWarnings({"WeakerAccess"})
+ @Nullable Boolean mNullableBoolean = null;
- // Code below generated by codegen v1.0.7.
+
+ // Code below generated by codegen v1.0.8.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -65,7 +73,8 @@
@NonNull Map<String,SampleWithCustomBuilder> map,
@NonNull Map<String,String> stringMap,
@NonNull SparseArray<SampleWithCustomBuilder> sparseArray,
- @NonNull SparseIntArray sparseIntArray) {
+ @NonNull SparseIntArray sparseIntArray,
+ @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean nullableBoolean) {
this.mStringArray = stringArray;
AnnotationValidations.validate(
NonNull.class, null, mStringArray);
@@ -87,6 +96,7 @@
this.mSparseIntArray = sparseIntArray;
AnnotationValidations.validate(
NonNull.class, null, mSparseIntArray);
+ this.mNullableBoolean = nullableBoolean;
// onConstructed(); // You can define this method to get a callback
}
@@ -126,6 +136,11 @@
return mSparseIntArray;
}
+ @DataClass.Generated.Member
+ public @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean getNullableBoolean() {
+ return mNullableBoolean;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -139,7 +154,8 @@
"map = " + mMap + ", " +
"stringMap = " + mStringMap + ", " +
"sparseArray = " + mSparseArray + ", " +
- "sparseIntArray = " + mSparseIntArray +
+ "sparseIntArray = " + mSparseIntArray + ", " +
+ "nullableBoolean = " + mNullableBoolean +
" }";
}
@@ -149,6 +165,9 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ int flg = 0;
+ if (mNullableBoolean != null) flg |= 0x80;
+ dest.writeInt(flg);
dest.writeStringArray(mStringArray);
dest.writeIntArray(mIntArray);
dest.writeStringList(mStringList);
@@ -156,6 +175,7 @@
dest.writeMap(mStringMap);
dest.writeSparseArray(mSparseArray);
dest.writeSparseIntArray(mSparseIntArray);
+ if (mNullableBoolean != null) dest.writeBoolean(mNullableBoolean);
}
@Override
@@ -169,6 +189,7 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ int flg = in.readInt();
String[] stringArray = in.createStringArray();
int[] intArray = in.createIntArray();
List<String> stringList = new java.util.ArrayList<>();
@@ -179,6 +200,7 @@
in.readMap(stringMap, String.class.getClassLoader());
SparseArray<SampleWithCustomBuilder> sparseArray = (SparseArray) in.readSparseArray(SampleWithCustomBuilder.class.getClassLoader());
SparseIntArray sparseIntArray = (SparseIntArray) in.readSparseIntArray();
+ Boolean nullableBoolean = (flg & 0x80) == 0 ? null : (Boolean) in.readBoolean();
this.mStringArray = stringArray;
AnnotationValidations.validate(
@@ -201,6 +223,7 @@
this.mSparseIntArray = sparseIntArray;
AnnotationValidations.validate(
NonNull.class, null, mSparseIntArray);
+ this.mNullableBoolean = nullableBoolean;
// onConstructed(); // You can define this method to get a callback
}
@@ -233,6 +256,7 @@
private @NonNull Map<String,String> mStringMap;
private @NonNull SparseArray<SampleWithCustomBuilder> mSparseArray;
private @NonNull SparseIntArray mSparseIntArray;
+ private @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean mNullableBoolean;
private long mBuilderFieldsSet = 0L;
@@ -328,10 +352,18 @@
return this;
}
+ @DataClass.Generated.Member
+ public @NonNull Builder setNullableBoolean(@SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mNullableBoolean = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public ParcelAllTheThingsDataClass build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
+ mBuilderFieldsSet |= 0x100; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mStringArray = null;
@@ -354,6 +386,9 @@
if ((mBuilderFieldsSet & 0x40) == 0) {
mSparseIntArray = null;
}
+ if ((mBuilderFieldsSet & 0x80) == 0) {
+ mNullableBoolean = null;
+ }
ParcelAllTheThingsDataClass o = new ParcelAllTheThingsDataClass(
mStringArray,
mIntArray,
@@ -361,12 +396,13 @@
mMap,
mStringMap,
mSparseArray,
- mSparseIntArray);
+ mSparseIntArray,
+ mNullableBoolean);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
+ if ((mBuilderFieldsSet & 0x100) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -374,10 +410,10 @@
}
@DataClass.Generated(
- time = 1570576454326L,
- codegenVersion = "1.0.7",
+ time = 1570828331396L,
+ codegenVersion = "1.0.8",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
- inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
+ inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings({\"WeakerAccess\"}) @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 14010a9..d014d6d 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -342,13 +342,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.8.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@IntDef(prefix = "STATE_", value = {
@@ -1868,8 +1872,8 @@
}
@DataClass.Generated(
- time = 1570576452225L,
- codegenVersion = "1.0.7",
+ time = 1570828329319L,
+ codegenVersion = "1.0.8",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_UNDEFINED\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index b5f6c73..1c87e8f 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,13 +85,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.8.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -249,8 +253,8 @@
}
@DataClass.Generated(
- time = 1570576453295L,
- codegenVersion = "1.0.7",
+ time = 1570828330331L,
+ codegenVersion = "1.0.8",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index 0ce8aba..27af37f 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -51,18 +51,22 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.8.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated(
- time = 1570576457249L,
- codegenVersion = "1.0.7",
+ time = 1570828334384L,
+ codegenVersion = "1.0.8",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
inputSignatures = "public @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
@Deprecated
diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING
index 55a6147..f34c432 100644
--- a/tests/FlickerTests/TEST_MAPPING
+++ b/tests/FlickerTests/TEST_MAPPING
@@ -1,7 +1,8 @@
{
"postsubmit": [
{
- "name": "FlickerTests"
+ "name": "FlickerTests",
+ "keywords": ["primary-device"]
}
]
}
\ No newline at end of file
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 231d045b..085c53c 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,7 +19,6 @@
static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
test_suites: ["general-tests"],
test_config: "RollbackTest.xml",
- // TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi
}
java_test_host {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 9e6ac8e..8b97f61 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -92,7 +92,7 @@
* Enable rollback phase.
*/
@Test
- public void testBadApkOnlyEnableRollback() throws Exception {
+ public void testBadApkOnly_Phase1() throws Exception {
Uninstall.packages(TestApp.A);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -101,9 +101,6 @@
InstallUtils.processUserData(TestApp.A);
Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testBadApkOnlyConfirmEnableRollback().
}
/**
@@ -111,7 +108,7 @@
* Confirm that rollback was successfully enabled.
*/
@Test
- public void testBadApkOnlyConfirmEnableRollback() throws Exception {
+ public void testBadApkOnly_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
@@ -122,9 +119,6 @@
assertThat(rollback).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(rollback.isStaged()).isTrue();
-
- // At this point, the host test driver will run
- // testBadApkOnlyTriggerRollback().
}
/**
@@ -133,15 +127,14 @@
* rebooting the test out from under it.
*/
@Test
- public void testBadApkOnlyTriggerRollback() throws Exception {
+ public void testBadApkOnly_Phase3() throws Exception {
// Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
- // We expect the device to be rebooted automatically. Wait for that to
- // happen. At that point, the host test driver will wait for the
- // device to come back up and run testApkOnlyConfirmRollback().
+ // We expect the device to be rebooted automatically. Wait for that to happen.
Thread.sleep(30 * 1000);
+ // Raise an error anyway if reboot didn't happen.
fail("watchdog did not trigger reboot");
}
@@ -150,7 +143,7 @@
* Confirm rollback phase.
*/
@Test
- public void testBadApkOnlyConfirmRollback() throws Exception {
+ public void testBadApkOnly_Phase4() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -177,8 +170,11 @@
networkStack)).isNull();
}
+ /**
+ * Stage install ModuleMetadata package to simulate a Mainline module update.
+ */
@Test
- public void installModuleMetadataPackage() throws Exception {
+ public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
resetModuleMetadataPackage();
Context context = InstrumentationRegistry.getContext();
PackageInfo metadataPackageInfo = context.getPackageManager().getPackageInfo(
@@ -192,6 +188,26 @@
+ metadataApkPath);
}
+ /**
+ * Verify the rollback is available.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollback_Phase2() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ MODULE_META_DATA_PACKAGE)).isNotNull();
+ }
+
+ /**
+ * Verify the rollback is committed after crashing.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollback_Phase3() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ MODULE_META_DATA_PACKAGE)).isNotNull();
+ }
+
@Test
public void assertNetworkStackRollbackAvailable() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
@@ -213,20 +229,6 @@
getNetworkStackPackageName())).isNull();
}
- @Test
- public void assertModuleMetadataRollbackAvailable() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- MODULE_META_DATA_PACKAGE)).isNotNull();
- }
-
- @Test
- public void assertModuleMetadataRollbackCommitted() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- MODULE_META_DATA_PACKAGE)).isNotNull();
- }
-
private String getNetworkStackPackageName() {
Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
ComponentName comp = intent.resolveSystemService(
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index bc98f06..2043027 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -68,35 +68,35 @@
*/
@Test
public void testBadApkOnly() throws Exception {
- runPhase("testBadApkOnlyEnableRollback");
+ runPhase("testBadApkOnly_Phase1");
getDevice().reboot();
- runPhase("testBadApkOnlyConfirmEnableRollback");
+ runPhase("testBadApkOnly_Phase2");
try {
// This is expected to fail due to the device being rebooted out
// from underneath the test. If this fails for reasons other than
// the device reboot, those failures should result in failure of
// the testApkOnlyConfirmRollback phase.
CLog.logAndDisplay(LogLevel.INFO, "testBadApkOnlyTriggerRollback is expected to fail");
- runPhase("testBadApkOnlyTriggerRollback");
+ runPhase("testBadApkOnly_Phase3");
} catch (AssertionError e) {
// AssertionError is expected.
}
getDevice().waitForDeviceAvailable();
- runPhase("testBadApkOnlyConfirmRollback");
+ runPhase("testBadApkOnly_Phase4");
}
@Test
public void testNativeWatchdogTriggersRollback() throws Exception {
//Stage install ModuleMetadata package - this simulates a Mainline module update
- runPhase("installModuleMetadataPackage");
+ runPhase("testNativeWatchdogTriggersRollback_Phase1");
// Reboot device to activate staged package
getDevice().reboot();
getDevice().waitForDeviceAvailable();
- runPhase("assertModuleMetadataRollbackAvailable");
+ runPhase("testNativeWatchdogTriggersRollback_Phase2");
// crash system_server enough times to trigger a rollback
crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
@@ -113,7 +113,7 @@
getDevice().waitForDeviceAvailable();
// verify rollback committed
- runPhase("assertModuleMetadataRollbackCommitted");
+ runPhase("testNativeWatchdogTriggersRollback_Phase3");
}
/**
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bffbbfd..cf3fba8 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -5709,7 +5709,6 @@
}
@Test
- @FlakyTest(bugId = 140305678)
public void testTcpBufferReset() throws Exception {
final String testTcpBufferSizes = "1,2,3,4,5,6";
final NetworkRequest networkRequest = new NetworkRequest.Builder()
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index ba00264..1a7fd6e 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -191,7 +191,7 @@
* Parcel.write* and Parcel.read* method name wildcard values
*/
val ParcelMethodsSuffix = when {
- FieldClass in PRIMITIVE_TYPES - "char" - "boolean" +
+ FieldClass in PRIMITIVE_TYPES - "char" - "boolean" + BOXED_PRIMITIVE_TYPES +
listOf("String", "CharSequence", "Exception", "Size", "SizeF", "Bundle",
"FileDescriptor", "SparseBooleanArray", "SparseIntArray", "SparseArray") ->
FieldClass
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 5a95676..0ebb3cf 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -854,6 +854,7 @@
it.nameAsString == intOrStringDef?.AnnotationName
|| it.nameAsString in knownNonValidationAnnotations
|| it in perElementValidations
+ || it.args.any { (_, value) -> value is ArrayInitializerExpr }
}.forEach { annotation ->
appendValidateCall(annotation,
valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
@@ -874,14 +875,7 @@
val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
"$validate(" {
!"${annotation.nameAsString}.class, null, $valueToValidate"
- val params = when (annotation) {
- is MarkerAnnotationExpr -> emptyMap()
- is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
- is NormalAnnotationExpr ->
- annotation.pairs.map { it.name.asString() to it.value }.toMap()
- else -> throw IllegalStateException()
- }
- params.forEach { name, value ->
+ annotation.args.forEach { name, value ->
!",\n\"$name\", $value"
}
}
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index 24cf469..d6953c0 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -87,6 +87,14 @@
is IntegerLiteralExpr -> sb.append(ex.asInt()).append("L")
is LongLiteralExpr -> sb.append(ex.asLong()).append("L")
is DoubleLiteralExpr -> sb.append(ex.asDouble())
+ is ArrayInitializerExpr -> {
+ sb.append("{")
+ ex.values.forEachLastAware { arrayElem, isLast ->
+ appendExpr(sb, arrayElem)
+ if (!isLast) sb.append(", ")
+ }
+ sb.append("}")
+ }
else -> sb.append(ex)
}
}
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index 039f7b2..ce83d3d 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -9,6 +9,7 @@
const val INDENT_SINGLE = " "
val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
+val BOXED_PRIMITIVE_TYPES = PRIMITIVE_TYPES.map { it.capitalize() } - "Int" + "Integer" - "Char" + "Character"
val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern")
@@ -142,6 +143,10 @@
//
// To regenerate run:
// $ $cliExecutable ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
"""
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 47f774f..8c4583f 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.7"
+const val CODEGEN_VERSION = "1.0.8"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index a1f068a..e703397 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -1,9 +1,6 @@
package com.android.codegen
-import com.github.javaparser.ast.Modifier
-import com.github.javaparser.ast.expr.AnnotationExpr
-import com.github.javaparser.ast.expr.Expression
-import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
import java.time.Instant
import java.time.ZoneId
@@ -88,3 +85,10 @@
}
fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}"
+
+val AnnotationExpr.args: Map<String, Expression> get() = when (this) {
+ is MarkerAnnotationExpr -> emptyMap()
+ is SingleMemberAnnotationExpr -> mapOf("value" to memberValue)
+ is NormalAnnotationExpr -> pairs.map { it.name.asString() to it.value }.toMap()
+ else -> throw IllegalArgumentException("Unknown annotation expression: $this")
+}
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 14eead8..1390f63 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -29,7 +29,7 @@
"-Werror",
],
- shared_libs: ["libprotoc"],
+ static_libs: ["libprotoc"],
}
cc_binary_host {